この記事では、java に関する関連知識を提供します。主に、排他的ロック、悲観的ロック、楽観的ロック、共有ロックなどの Java ロックに関連する問題について紹介します。見てみましょう。皆様のお役に立てれば幸いです。
## 推奨学習: 「Java ビデオ チュートリアル 」
楽観的ロックと悲観的ロック悲観的ロック
悲観的なロックは、人生において悲観的な人々に対応します。悲観的な人々は、物事が間違った方向に進むことを常に考えています。
synchronized と
ReentrantLock が典型的な悲観的ロックであり、一部では synchronized キーワードが使用されます。
HashTable も悲観的ロックのアプリケーションです。
楽観的なロック
楽観的なロック 人生において楽観的な人に相当します 楽観的な人は、物事が良い方向に発展することを常に考えています。
バージョン番号メカニズムと
CAS アルゴリズムを使用して実装できます。 Java 言語では、
java.util.concurrent.atomic パッケージの下のアトミック クラスは、CAS オプティミスティック ロックを使用して実装されます。
2 つのロックの使用シナリオ
悲観的ロックと楽観的ロックに優劣はなく、それぞれに適したシナリオがあります。 オプティミスティック ロックは、書き込みが比較的少ない (競合が比較的小さい) シナリオに適しています。ロックをロックまたは解放する必要がないため、ロックのオーバーヘッドが排除され、スループットが向上します。 書き込みが多く読み取りが少ないシナリオ、つまり競合が深刻でスレッド間の競争が刺激されている場合、楽観的ロックを使用するとスレッドが継続的に再試行することになり、パフォーマンスが低下する可能性があります。このシナリオでは、悲観的ロックを使用する方が適切です。 排他ロックと共有ロック排他ロック
排他ロックは、ロックを 1 つのスレッドのみが保持できることを意味します。一度に持っています。スレッドがデータに排他ロックを追加すると、他のスレッドはデータにいかなる種類のロックも追加できなくなります。排他ロックを取得したスレッドは、データの読み取りと変更の両方を行うことができます。
synchronized および
java.util.concurrent(JUC) パッケージの Lock の実装クラスは、排他的ロック。
共有ロック
共有ロックは、ロックを複数のスレッドで保持できることを意味します。スレッドがデータに共有ロックを追加した場合、他のスレッドはデータに共有ロックを追加することしかできず、排他ロックを追加することはできません。共有ロックを取得したスレッドはデータの読み取りのみが可能で、データを変更することはできません。
ReentrantReadWriteLock は共有ロックです。
ミューテックス ロック
##ミューテックス ロックは従来型の排他ロックです。実装とは、次のことを意味します。特定のリソースには同時に 1 人の訪問者のみがアクセスでき、ユニークかつ排他的です。
#ミューテックス ロック ミューテックス ロックを所有できるのは一度に 1 つのスレッドだけであり、他のスレッドは待機することしかできません。
読み取り/書き込みロック読み取り/書き込みロック
は、共有ロックの特定の実装です。読み取り/書き込みロックは、一連のロックを管理します。1 つは読み取り専用ロックで、もう 1 つは書き込みロックです。 書き込みロックがなく、書き込みロックが排他的である場合、読み取りロックは複数のスレッドによって同時に保持される可能性があります。書き込みロックの優先順位は読み取りロックの優先順位よりも高く、読み取りロックを取得したスレッドは、以前に解放された書き込みロックによって更新された内容を参照できる必要があります。
読み取り/書き込みロックは、ミューテックス ロックよりも高い同時実行性を持っています。一度に書き込みスレッドは 1 つだけですが、複数のスレッドが同時に読み取りを行うことができます。
読み取り/書き込みロック インターフェイスは JDK で定義されています: ReadWriteLock
public interface ReadWriteLock { /** * 获取读锁 */ Lock readLock(); /** * 获取写锁 */ Lock writeLock(); }
ReentrantReadWriteLock
実装 ReadWriteLock
インターフェイスの特定の実装についてはここでは展開しません。ソース コードは後で詳しく分析します。
公平なロック
公平なロック
とは、複数のスレッドがロックを適用する順序を指します。鍵を取りに来てください。チケットを買うために列に並ぶようなものです。先に来た人が先に購入し、後から来た人は列の最後尾に並びます。これは公平です。
Java では、公平なロックはコンストラクターを通じて初期化できます
/** * 创建一个可重入锁,true 表示公平锁,false 表示非公平锁。默认非公平锁 */ Lock lock = new ReentrantLock(true);
不公平なロック
不公平なロック
とは、複数のスレッドがロックを取得する順序が、ロックを適用する順序ではないことを意味します。最初に適用するスレッドよりも前に、後から適用するスレッドがロックを取得する可能性があります。同時実行性の高い環境では、これにより、優先順位の反転、または飢餓状態 (スレッドがロックを取得しない) が発生する可能性があります。
Java では、synchronized キーワードは不公平なロックであり、ReentrantLock もデフォルトでは不当なロックです。
/** * 创建一个可重入锁,true 表示公平锁,false 表示非公平锁。默认非公平锁 */ Lock lock = new ReentrantLock(false);
リエントラント ロック
は、再帰ロック
とも呼ばれます。これは、同じスレッドが外部メソッド Lock でロックを取得することを意味します。ロックは内部メソッドに入るときに自動的に取得されます。
Java ReentrantLock の場合、その名前によって、それがリエントラント ロックであることがわかります。同期の場合、これは再入可能なロックでもあります。
黒板をノックしてください: リエントラント ロックの利点の 1 つは、デッドロックをある程度回避できることです。
同期を例として、次のコードを見てください:
public synchronized void mehtodA() throws Exception{ // Do some magic tings mehtodB(); } public synchronized void mehtodB() throws Exception{ // Do some magic tings }
上記のコードでは、メソッド A がメソッド B を呼び出します。スレッドがメソッド A を呼び出し、ロックを取得してからメソッド B を呼び出すと、ロックされている、これはリエントラント ロックの特性です。リエントラント ロックではない場合、現在のスレッドでは mehtodB が実行されず、デッドロックが発生する可能性があります。
スピン ロック
は、ロックが取得されなかったときにスレッドが直接中断されず、ビジー ループを実行することを意味します。スピンと呼ばれます。
#スレッドの一時停止とウェイクアップはリソースを消費する操作でもあるため、スピン ロックの目的は、スレッドが一時停止される可能性を減らすことです。
ロックが別のスレッドによって長時間占有されている場合、現在のスレッドはスピン後も中断されたままになり、ビジー ループがシステム リソースの無駄になり、実際に全体のパフォーマンスが低下します。したがって、スピン ロックは、ロックに時間がかかる同時実行状況には適していません。
Java では、AtomicInteger
クラスにスピン操作があります。コードを見てみましょう:
public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, v + delta)); return v; }
CAS 操作が失敗した場合、取得するためにループし続けます。現在の値を変更して再試行してください。
さらに、適応スピン ロックについても理解する必要があります。
アダプティブ スピンは JDK1.6 で導入されました。これはよりインテリジェントです。スピン時間は固定されなくなりました。同じロック上の以前のスピン時間とロック所有者のステータスによって決定されます。決定する。仮想マシンがこのスピンが再度成功する可能性が高いと判断した場合は、さらに時間がかかります。スピンがめったに成功しない場合は、プロセッサ リソースの無駄を避けるために、将来スピン プロセスを直接省略する可能性があります。
セグメント ロック
はロックの設計であり、特定のロックではありません。
セグメント化されたロックの設計目的は、ロックの粒度をさらに調整することです。操作で配列全体を更新する必要がない場合は、配列内の 1 つの項目のみをロックできます。
Java 言語では、CurrentHashMap の基層でセグメンテーション ロックが使用されており、Segment を使用することで同時に使用することができます。
パフォーマンスを向上させ、ロックの取得と解放による消費を削減するために、JDK1.6 では 4 つのロック状態が導入されました。 : ロックなし
、偏ったロック
、軽量ロック
、およびヘビーウェイト ロック
(ロックの数によって変化します) スレッドの競合状況徐々にエスカレートしますが、悪化することはありません。
ロックフリー
ロックフリー
この状態は実際には上記の楽観的ロックなので、ここでは詳しく説明しません。
バイアスロック
Java偏向锁(Biased Locking)是指它会偏向于第一个访问锁的线程,如果在运行过程中,只有一个线程访问加锁的资源,不存在多线程竞争的情况,那么线程是不需要重复获取锁的,这种情况下,就会给线程加一个偏向锁。
偏向锁的实现是通过控制对象Mark Word
的标志位来实现的,如果当前是可偏向状态
,需要进一步判断对象头存储的线程 ID 是否与当前线程 ID 一致,如果一致直接进入。
轻量级锁
当线程竞争变得比较激烈时,偏向锁就会升级为轻量级锁
,轻量级锁认为虽然竞争是存在的,但是理想情况下竞争的程度很低,通过自旋方式
等待上一个线程释放锁。
重量级锁
如果线程并发进一步加剧,线程的自旋超过了一定次数,或者一个线程持有锁,一个线程在自旋,又来了第三个线程访问时(反正就是竞争继续加大了),轻量级锁就会膨胀为重量级锁
,重量级锁会使除了此时拥有锁的线程以外的线程都阻塞。
升级到重量级锁其实就是互斥锁了,一个线程拿到锁,其余线程都会处于阻塞等待状态。
在 Java 中,synchronized 关键字内部实现原理就是锁升级的过程:无锁 --> 偏向锁 --> 轻量级锁 --> 重量级锁。这一过程在后续讲解 synchronized 关键字的原理时会详细介绍。
锁粗化
锁粗化
就是将多个同步块的数量减少,并将单个同步块的作用范围扩大,本质上就是将多次上锁、解锁的请求合并为一次同步请求。
举个例子,一个循环体中有一个代码同步块,每次循环都会执行加锁解锁操作。
private static final Object LOCK = new Object(); for(int i = 0;i <p>经过<code>锁粗化</code>后就变成下面这个样子了:</p><pre class="brush:php;toolbar:false"> synchronized(LOCK){ for(int i = 0;i <p><strong>锁消除</strong></p><p><code>锁消除</code>是指虚拟机编译器在运行时检测到了共享数据没有竞争的锁,从而将这些锁进行消除。</p><p>举个例子让大家更好理解。</p><pre class="brush:php;toolbar:false">public String test(String s1, String s2){ StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append(s1); stringBuffer.append(s2); return stringBuffer.toString(); }
上面代码中有一个 test 方法,主要作用是将字符串 s1 和字符串 s2 串联起来。
test 方法中三个变量s1, s2, stringBuffer, 它们都是局部变量,局部变量是在栈上的,栈是线程私有的,所以就算有多个线程访问 test 方法也是线程安全的。
我们都知道 StringBuffer 是线程安全的类,append 方法是同步方法,但是 test 方法本来就是线程安全的,为了提升效率,虚拟机帮我们消除了这些同步锁,这个过程就被称为锁消除
。
StringBuffer.class // append 是同步方法 public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; }
前面讲了 Java 语言中各种各种的锁,最后再通过六个问题统一总结一下:
推荐学习:《java视频教程》
以上がJavaロックを完全マスター(グラフィックとテキストの分析)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。