無料学習の推奨事項: Java 基本チュートリアル
ロック ロックとプロデューサー/コンシューマーの問題
従来の同期ロック
基本的なチケット販売の例を実装します:
/* 真正的多线程开发,公司中的开发,降低耦合性 线程就是一个单独的资源类,没有任何附属的操作 1.属性,方法 * */public class SaleTicketDemo1 { public static void main(String[] args) { //并发,多个线程操作同一个资源类,把资源类丢入线程 Ticket ticket=new Ticket(); //Runnable借口是一个FunationalInterface函数式接口,接口可以new,jdk1.8以后,lamda表达式()->{代码} new Thread(()->{ for(int i=0;i{ for(int i=0;i0){ System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余"+number); } }}
ここではラムダ式が使用されていることに注意してください。ラムダ式の詳細な説明については、「Java の基礎 - ラムダ式」を参照してください
これは、同時実行性を実現するために従来の同期を使用することです。同期の本質はキューとロックです。カフェテリアに並ぶようなものです。行列がないと混乱してしまいます。一人へのサービスが完了して初めて、他の人もサービスを受けることができます。
Lock
前述したように、JVM は変数への同期アクセスを実現し、wait と notification を使用してスレッド間通信を実装するための synchronized キーワードを提供します。 jdk1.5以降、JAVAではsynchronizedと同様の機能を実装するためのLockクラスが提供され、またスレッド間の通信を表示するためのConditionも提供されています。
Lock クラスは Java クラスが提供する機能であり、豊富な API により Lock クラスの同期機能は同期よりも強力です。
java.util.Concurrent パッケージには、Condition と lock (標準ロック) の 3 つのインターフェイスがあります。 ReadWriteLock ロック (読み取り/書き込みロック)
Lock 実装は、同期されたメソッドやステートメントを使用して取得できるよりも広範囲のロック操作を提供します。これらにより、より柔軟な構造化が可能になり、完全に異なるプロパティを持つことができ、関連する複数のオブジェクト条件をサポートできます。
Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }
lock() はロック、unlock() はロック解除を意味します
JDK 公式ドキュメントで説明されています
すべて既知の実装クラス:
ReentrantLock リエントラント ロック
ReentrantReadWriteLock.ReadLock 読み取りロック
ReentrantReadWriteLock.writeLock 書き込みロック
まず、ReentrantLock 実装クラスについて説明します。 :
ReentrantLock の基礎となるソース コード コンストラクター
公平なロック: 非常に公平で、早い者勝ちです。しかし問題は、3s と 3h のプロセスが到着した場合、3h が最初に来て、その後 3s が 3h 待機することですが、これは実際には良くありません。
不公平なロック: 非常に不公平です。キューをジャンプできます (デフォルト)
これについては後で詳しく説明します。
使い方、使用前ロック、使用後のロック解除
//ロックロックトリロジー
//1.new ReentranLock() ;構築
//2.Lock.lock();Lock
//3.finally();Unlock
public class SaleTicketDemo2 { public static void main(String[] args) { //并发,多个线程操作同一个资源类,把资源类丢入线程 Ticket ticket=new Ticket(); //Runnable借口是一个FunationalInterface函数式接口,接口可以new,jdk1.8以后,lamda表达式()->{代码} new Thread(()->{for(int i=0;i{for(int i=0;i{for(int i=0;i0){ System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余"+number); } } catch (Exception e) { // TODO: handle exception }finally{ lock.unlock(); } }}
Synchronized と lock lock の違い
1.synchronized は組み込みの Java キーワード、lock は Java クラスです
2.synchronized はロックの取得ステータスを判断できません、lock はロックが取得されたかどうかを判断できます
3.同期は自動的に行われます。ロックを解除するには (a-)、手動でロックを解除する必要があります。ロックが解放されない場合、デッドロックが発生します。
4. 同期スレッド 1 (ロックの取得、ブロック)、スレッド 2 (待機、愚かな待機)
lock.tryLock() はロックの取得を試みますが、常に起こるとは限りません。お待ちください
5. 同期された再入可能なロック、中断できない不公平なロック。ロック、リエントラントロック、ロックを判定可能、公平・不公平を自分で設定可能(自分で設定可能)
6. synchronized は少量のコード同期問題に適し、lock lock は大量のコード同期の問題に適しています。同期されたコードの量
同期されたロック オブジェクトと同期されたコード ブロック メソッド
従来のプロデューサーとコンシューマーの問題
従来のプロデューサーとコンシューマーは待機通知メソッドに基づいていますこれを実現するために、オブジェクト クラスのワードと同期キーが使用されます。
面接中に、プロデューサーとコンシューマーのコードを手書きすることは非常に一般的です。
面接筆記試験の古典的な質問:
シングルトン モードの並べ替えアルゴリズム プロデューサーとコンシューマーのデッドロック
プロデューサーとコンシューマーの問題の同期バージョン
スレッド間の通信の問題: プロデューサー ウェイク待ち-up with Consumer issues, notification wake-up
スレッドは同じ変数を操作するために A B を交互に実行します。number=0
A num 1
B num-1
注: はlocked メソッドでは、業務通知を判断して待つという実行の考え方です。
package testConcurrent;/* 线程之间的通信问题:生产者和消费者问题 等待唤醒,通知唤醒 线程交替执行 A B 操作同一个变量number=0 A num+1 B num-1 * */public class A { public static void main(String[] args) { Data data =new Data(); new Thread(()->{ for(int i=0;i{ for(int i=0;i"+number); //通知其他线程,我+1完毕了 this.notify(); } //-1 public synchronized void decrement() throws InterruptedException{ if(number==0){ //等待 this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程,我-1完毕了 this.notify(); }}
図に示すように、基本的に必要な機能は実現できますただし、問題は引き続き発生します。この時点で 2 つのスレッドを追加して再試行すると、
new Thread(()->{ for(int i=0;i{ for(int i=0;i<p><img src="https://img.php.cn/upload/article/000/000/052/4011c6e510a595c0afc39326e031cf33-4.png" alt="Java ではロックとプロデューサー/コンシューマーの問題が発生します"><br> 这里结果中出现了2,输出结果出现了问题。为什么呢?<br> 为什么if判断会出现问题:<br> if判断只判断一次。因为if判断了之后,就已经进入了代码的等待那一行,这时,在wait下的线程可能有多个,甚至包括生产者和消费者。有可能某个生产者执行完了之后,唤醒的是另一个生产者。</p><p>在我们的官方文档中就给出了解释</p><pre class="brush:php;toolbar:false">public final void wait(long timeout) throws InterruptedException
导致当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或指定的时间已过。
线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:
synchronized (obj) { while (<condition>) obj.wait(timeout); ... // Perform action appropriate to condition }</condition>
注意点:防止虚假唤醒问题。
我们代码中用的是if判断,而应该用while判断
package testConcurrent;/* 线程之间的通信问题:生产者和消费者问题 等待唤醒,通知唤醒 线程交替执行 A B 操作同一个变量number=0 A num+1 B num-1 * */public class A { public static void main(String[] args) { Data data =new Data(); new Thread(()->{ for(int i=0;i{ for(int i=0;i{ for(int i=0;i{ for(int i=0;i"+number); //通知其他线程,我+1完毕了 this.notify(); } //-1 public synchronized void decrement() throws InterruptedException{ while(number==0){ //等待 this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程,我-1完毕了 this.notify(); }}
Lock版的生产者和消费者问题
在synchronized版本中,我们使用了wait和notify来实现线程之间的同步
在lock中,
此时synchronized被lock替换了,那么wait和notify用什么来替换呢?
我们在官方文档java.util.concurrent.locks 中,找到Lock类,然后在底部找到了
Condition newCondition()
返回一个新Condition绑定到该实例Lock实例。
在等待条件之前,锁必须由当前线程保持。 呼叫Condition.await()将在等待之前将原子释放锁,并在等待返回之前重新获取锁。
然后我们再来了解Condition类
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
一个Condition实例本质上绑定到一个锁。 要获得特定Condition实例的Condition实例,请使用其newCondition()方法。
我们可以看到,使用的时候new一个Condition对象。然后用await替代wait,signal替换notify
代码实现
//判断等待+业务+通知
class Data2{ //数字。资源类 private int number=0; Lock lock=new ReentrantLock(); Condition condition=lock.newCondition(); //+1 public void increment() throws InterruptedException{ try { lock.lock(); //业务代码 while(number!=0){ //等待 condition.await(); } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); condition.signalAll(); //通知 } catch (Exception e) { // TODO: handle exception }finally{ lock.unlock(); } } //-1 public void decrement() throws InterruptedException{ try { lock.lock(); //业务代码 while(number!=1){ //等待 condition.await(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); condition.signalAll(); //通知 } catch (Exception e) { // TODO: handle exception }finally{ lock.unlock(); } } }
注意:主函数部分于最上面的代码一样。
这时候虽然说是正确的,但是它是一个随机分布的状态,现在我们希望它有序执行,即A执行完了执行B,B执行C,C完了执行D。即精准通知。
Condition实现精准通知唤醒
Condition实现精准的通知和唤醒
我们构造三个线程,要求A执行完了执行B,B执行完了执行C,C执行完了执行D.
代码思想:
//加多个监视器,通过监视器来判断唤醒的是哪一个人
//设置多个同步监视器,每个监视器监视一个线程
//实例:生产线,下单->支付->交易->物流
package testConcurrent;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/* A执行完调用B,B执行完调用C,C执行完调用A * */public class C { public static void main(String[] args) { Data3 data=new Data3(); new Thread(()->{ for(int i=0;i{ for(int i=0;i{ for(int i=0;i支付->交易->物流 private Condition condition1=lock.newCondition(); private Condition condition2=lock.newCondition(); private Condition condition3=lock.newCondition(); private int number=1; //1A 2B 3C public void printA(){ lock.lock(); try { //业务,判断->执行->通知 while(number!=1){ //等待 condition1.await(); } System.out.println(Thread.currentThread().getName()+"=>AAAAAAA"); //唤醒,唤醒指定的人,B number=2; //精准唤醒 condition2.signal(); } catch (Exception e) { // TODO: handle exception }finally{ lock.unlock(); } } public void printB(){ lock.lock(); try { //业务,判断->执行->通知 while(number!=2){ //等待 condition2.await(); } System.out.println(Thread.currentThread().getName()+"=>BBBBBBB"); //唤醒,唤醒指定的人,C number=3; //精准唤醒 condition3.signal(); } catch (Exception e) { // TODO: handle exception }finally{ lock.unlock(); } } public void printC(){ lock.lock(); try { //业务,判断->执行->通知 while(number!=3){ //等待 condition3.await(); } System.out.println(Thread.currentThread().getName()+"=>CCCCCCC"); //唤醒,唤醒指定的人,A number=1; //精准唤醒 condition1.signal(); } catch (Exception e) { // TODO: handle exception }finally{ lock.unlock(); } }}
相关学习推荐:java基础
以上がJava ではロックとプロデューサー/コンシューマーの問題が発生しますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。