目次
多対多の生産と消費:2羽の鶏とLian / Lianの妻と呼ばれる
前書き
マルチスレッド通信は、常に高頻度のインタビューのテストサイトでした。一部のインタビュアーは、マルチスレッドのスキルを調べるために、生産者/消費者コードをサイトで手書きするように依頼する場合があります。今日、コードを使用して実装プロセスを分析します。実生活で産卵する雌鶏の場合。鶏は鶏舎に産卵し、鶏は鶏舎から産卵します。鶏は鶏舎に産卵します。生産者は生産者です。鶏は卵を拾い、消費者は糸です。生産者では消費者モデルでは、鶏小屋は卵の入れ物です。実際には、病院の電話番号に電話をかけるなど、多くの場合があります。下の絵を描いてみましょう。
1対1の生産と消費:鶏とトレーニング
待つ/通知する
package com.duyang.thread.basic.waitLock.demo;
import java.util.ArrayList;
import java.util.List;
/**
* @author :jiaolian
* @date :Created in 2020-12-30 16:18
* @description:母鸡下蛋:一对一生产者和消费者
* @modified By:
* 公众号:叫练
*/
public class SingleNotifyWait {
//装鸡蛋的容器
private static class EggsList {
private static final List<String> LIST = new ArrayList();
}
//生产者:母鸡实体类
private static class HEN {
private String name;
public HEN(String name) {
this.name = name;
}
//下蛋
public void proEggs() throws InterruptedException {
synchronized (EggsList.class) {
if (EggsList.LIST.size() == 1) {
EggsList.class.wait();
}
//容器添加一个蛋
EggsList.LIST.add("1");
//鸡下蛋需要休息才能继续产蛋
Thread.sleep(1000);
System.out.println(name+":下了一个鸡蛋!");
//通知叫练捡蛋
EggsList.class.notify();
}
}
}
//人对象
private static class Person {
private String name;
public Person(String name) {
this.name = name;
}
//取蛋
public void getEggs() throws InterruptedException {
synchronized (EggsList.class) {
if (EggsList.LIST.size() == 0) {
EggsList.class.wait();
}
Thread.sleep(500);
EggsList.LIST.remove(0);
System.out.println(name+":从容器中捡出一个鸡蛋");
//通知叫练捡蛋
EggsList.class.notify();
}
}
}
public static void main(String[] args) {
//创造一个人和一只鸡
HEN hen = new HEN("小黑");
Person person = new Person("叫练");
//创建线程执行下蛋和捡蛋的过程;
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
hen.proEggs();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//叫练捡鸡蛋的过程!
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
person.getEggs();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
上記のコードのように、卵を保持するようにEggsListクラスを定義し、HENクラスは鶏を表し、Personクラスは人を表します。main関数で、鶏のオブジェクト「Xiao Hei」と人間のオブジェクト「callingpractice」を作成し、2つのスレッドを作成して、それぞれ産卵プロセスと採卵プロセスを実行します。鶏小屋に入れることができる卵は1つだけであることがコードで定義されています(もちろん、複数の卵を定義できます)。詳細なプロセス:「リトルブラック」の鶏のスレッドは、ロックの「呼び出し」スレッドと競合します。「リトルブラック」の鶏のスレッドが最初にロックを取得し、EggsListの卵の数が0より大きい場合は、そこにあることを意味します。卵である場合は、待機を呼び出して待機し、「呼び出し練習」スレッドへのロックを解除します。卵がない場合は、EggsList.LIST.add( "1")を呼び出して卵が生成されたことを示し、「呼び出し練習」に通知します。 「卵をフェッチし、「呼び出し練習」スレッドのロックを解除してロックを取得します。「呼び出し練習」スレッドがgetEggs()メソッドを呼び出してロックを取得した後、鶏小屋に卵がない場合は、待機を呼び出してロックを解放し、「XiaoHei」スレッドに取得を通知します。ロックして産卵します。卵がある場合は、「シャオヘイ」がすでに産卵していることを意味します。産卵時に鶏小屋に卵がないため、卵を取り出します。notify()メソッドは最後に「シャオヘイ」に産卵を通知するよう呼びかけ、下図のようにプログラムの実行結果を観察します。2つのスレッドは無限ループにあり、プログラムは引き続き実行されます。産卵と採卵のプロセスで使用されるロックは、EggsListクラスのクラスです。「Xiaohei」と「CallingLian」の競争は統一されています。ロックなので、これは同期です。これが、鶏「シャオヘイ」と「ジャオリアン」のコミュニケーションの仕方です。
何???鶏と人がコミュニケーションできる!!
ロック条件キュー
package com.duyang.thread.basic.waitLock.demo;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author :jiaolian
* @date :Created in 2020-12-30 16:18
* @description:母鸡下蛋:一对一生产者和消费者 条件队列
* @modified By:
* 公众号:叫练
*/
public class SingleCondition {
private static Lock lock = new ReentrantLock();
//条件队列
private static Condition condition = lock.newCondition();
//装鸡蛋的容器
private static class EggsList {
private static final List<String> LIST = new ArrayList();
}
//生产者:母鸡实体类
private static class HEN {
private String name;
public HEN(String name) {
this.name = name;
}
//下蛋
public void proEggs() {
try {
lock.lock();
if (EggsList.LIST.size() == 1) {
condition.await();
}
//容器添加一个蛋
EggsList.LIST.add("1");
//鸡下蛋需要休息才能继续产蛋
Thread.sleep(1000);
System.out.println(name+":下了一个鸡蛋!");
//通知叫练捡蛋
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
//人对象
private static class Person {
private String name;
public Person(String name) {
this.name = name;
}
//取蛋
public void getEggs() {
try {
lock.lock();
if (EggsList.LIST.size() == 0) {
condition.await();
}
Thread.sleep(500);
EggsList.LIST.remove(0);
System.out.println(name+":从容器中捡出一个鸡蛋");
//通知叫练捡蛋
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
//创造一个人和一只鸡
HEN hen = new HEN("小黑");
Person person = new Person("叫练");
//创建线程执行下蛋和捡蛋的过程;
new Thread(()->{
for (int i=0; i<Integer.MAX_VALUE;i++) {
hen.proEggs();
}
}).start();
//叫练捡鸡蛋的过程!
new Thread(()->{
for (int i=0; i<Integer.MAX_VALUE;i++) {
person.getEggs();
}
}).start();
}
}
上記のコードと同様に、同期のみがロックに置き換えられ、プログラム操作の結果は上記と同じになり、待機/通知はAQS条件キュー条件に置き換えられてスレッド間の通信が制御されます。ロックはlock.lock()を使用して手動でロックする必要があり、lock.unlock()のロックを解除するステップは、ロックを常に解放できるように、finallyコードブロックに配置されます。awaitの最下層は、unsafe.park(false、0)がC ++コードを呼び出すことによって実現されます。
多対多の生産と消費:2羽の鶏とLian / Lianの妻と呼ばれる
待機/通知すべて
package com.duyang.thread.basic.waitLock.demo;
import java.util.ArrayList;
import java.util.List;
/**
* @author :jiaolian
* @date :Created in 2020-12-30 16:18
* @description:母鸡下蛋:多对多生产者和消费者
* @modified By:
* 公众号:叫练
*/
public class MultNotifyWait {
//装鸡蛋的容器
private static class EggsList {
private static final List<String> LIST = new ArrayList();
}
//生产者:母鸡实体类
private static class HEN {
private String name;
public HEN(String name) {
this.name = name;
}
//下蛋
public void proEggs() throws InterruptedException {
synchronized (EggsList.class) {
while (EggsList.LIST.size() >= 10) {
EggsList.class.wait();
}
//容器添加一个蛋
EggsList.LIST.add("1");
//鸡下蛋需要休息才能继续产蛋
Thread.sleep(1000);
System.out.println(name+":下了一个鸡蛋!共有"+EggsList.LIST.size()+"个蛋");
//通知叫练捡蛋
EggsList.class.notify();
}
}
}
//人对象
private static class Person {
private String name;
public Person(String name) {
this.name = name;
}
//取蛋
public void getEggs() throws InterruptedException {
synchronized (EggsList.class) {
while (EggsList.LIST.size() == 0) {
EggsList.class.wait();
}
Thread.sleep(500);
EggsList.LIST.remove(0);
System.out.println(name+":从容器中捡出一个鸡蛋!还剩"+EggsList.LIST.size()+"个蛋");
//通知叫练捡蛋
EggsList.class.notify();
}
}
}
public static void main(String[] args) {
//创造一个人和一只鸡
HEN hen1 = new HEN("小黑");
HEN hen2 = new HEN("小黄");
Person jiaolian = new Person("叫练");
Person wife = new Person("叫练媳妇");
//创建线程执行下蛋和捡蛋的过程;
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
hen1.proEggs();
Thread.sleep(50);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
hen2.proEggs();
Thread.sleep(50);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//叫练捡鸡蛋的线程!
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
jiaolian.getEggs();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//叫练媳妇捡鸡蛋的线程!
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
wife.getEggs();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
上記のコードと同様に、1対1の生産と消費における待機/通知コードを参照していくつかの変更が加えられました.2つの鶏のスレッド「XiaoHei」と「XiaoHuang」が作成され、2つの卵摘みスレッドが作成されます下図のように「リアン」「リアンの妻」と呼ばれ、実行結果が同期され、多対多の生産と消費が実現されます。注意すべき点は次のとおりです。
- 鶏舎で最大の卵は10個です。
- 卵の数が10以上かどうかを判断するproEggs()メソッドでは、whileループが使用されます。Waitは通知を受信し、現在のスレッドをウェイクアップします。ロジックの問題を回避するために、再度判断する必要があります。プログラム。ここでは使用できません。ifを使用すると、プログラムEggsListに10個を超える卵が含まれる場合があります。このプログラムではエラーが発生しやすい場所であり、よく聞かれる点でもあり、調査する価値があります。
- 多対多の生産者と消費者。
ロック条件キュー
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author :jiaolian
* @date :Created in 2020-12-30 16:18
* @description:母鸡下蛋:多对多生产者和消费者 条件队列
* @modified By:
* 公众号:叫练
*/
public class MultCondition {
private static Lock lock = new ReentrantLock();
//条件队列
private static Condition condition = lock.newCondition();
//装鸡蛋的容器
private static class EggsList {
private static final List<String> LIST = new ArrayList();
}
//生产者:母鸡实体类
private static class HEN {
private String name;
public HEN(String name) {
this.name = name;
}
//下蛋
public void proEggs() {
try {
lock.lock();
while (EggsList.LIST.size() >= 10) {
condition.await();
}
//容器添加一个蛋
EggsList.LIST.add("1");
//鸡下蛋需要休息才能继续产蛋
Thread.sleep(1000);
System.out.println(name+":下了一个鸡蛋!共有"+ EggsList.LIST.size()+"个蛋");
//通知叫练/叫练媳妇捡蛋
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
//人对象
private static class Person {
private String name;
public Person(String name) {
this.name = name;
}
//取蛋
public void getEggs() throws InterruptedException {
try {
lock.lock();
while (EggsList.LIST.size() == 0) {
condition.await();
}
Thread.sleep(500);
EggsList.LIST.remove(0);
System.out.println(name+":从容器中捡出一个鸡蛋!还剩"+ EggsList.LIST.size()+"个蛋");
//通知叫练捡蛋
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
//创造一个人和一只鸡
HEN hen1 = new HEN("小黑");
HEN hen2 = new HEN("小黄");
Person jiaolian = new Person("叫练");
Person wife = new Person("叫练媳妇");
//创建线程执行下蛋和捡蛋的过程;
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
hen1.proEggs();
Thread.sleep(50);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
hen2.proEggs();
Thread.sleep(50);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//叫练捡鸡蛋的线程!
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
jiaolian.getEggs();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//叫练媳妇捡鸡蛋的线程!
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
wife.getEggs();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
上記のコードのように、同期をロックに置き換えるだけです。プログラム操作の結果は上記と同じです。ロックと同期の類似点と相違点を以下で比較してみましょう。この質問はインタビュー中によく聞かれます!
ロックと同期の比較
ロックと同期の両方で、複数のスレッドを同期できます。主な類似点と相違点は次のとおりです。
- ロックの性質:ロックの楽観的ロックは非ブロッキングであり、最下層はcas + volatileによって実装され、同期された悲観的ロックはブロッキングであり、コンテキストの切り替えが必要です。別の方法でアイデアを実現します。
- 機能の詳細:ロックは手動でロック解除する必要があり、同期は自動的にロック解除されます。Lockは、tryLockなどのより詳細な機能も提供します。
- スレッド通信:ロックは条件キューを提供し、1つのロックは複数の条件キューに対応でき、スレッド制御はより繊細です。同期は、1つの待機/通知にのみ対応できます。
同期、揮発性、およびcasのキーワードについてよく知らない場合は、以前の記事で詳細なケースと説明を読むことができます。
総括する
今日、私は実際の例を使用してコードに変換し、2つのマルチスレッドコンシューマー/プロデューサーモードを実現しました。コードをもう一度入力することをお勧めします。コードを注意深く実行すれば、理解できるはずです。気に入ったら、「いいね」をクリックしてフォローしてください。私の名前はリアン[公式アカウント]です。電話して練習します。