産卵鶏の例:マルチスレッド通信のプロデューサーとコンシューマーの待機/通知および条件/待機/シグナル条件キュー

目次

 

前書き

1対1の生産と消費:鶏とトレーニング

待つ/通知する

ロック条件キュー

多対多の生産と消費:2羽の鶏とLian / Lianの妻と呼ばれる

待機/通知すべて

ロック条件キュー

ロックと同期の比較

総括する


前書き


マルチスレッド通信は、常に高頻度のインタビューのテストサイトでした。一部のインタビュアーは、マルチスレッドのスキルを調べるために、生産者/消費者コードをサイト手書きするように依頼する場合があります。今日、コードを使用して実装プロセスを分析します。実生活で産卵する雌鶏の場合。鶏は鶏舎に産卵し、鶏は鶏舎から産卵します。鶏は鶏舎に産卵します。生産者は生産者です。鶏は卵を拾い、消費者は糸です。生産者では消費者モデルでは、鶏小屋は卵の入れ物です。実際には、病院の電話番号に電話をかけるなど、多くの場合があります。下の絵を描いてみましょう。

image.png

 

 

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」の競争は統一されています。ロックなので、これは同期です。これが、鶏「シャオヘイ」と「ジャオリアン」のコミュニケーションの仕方です。

image.png

何?鶏と人がコミュニケーションできる!

image.png

ロック条件キュー

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つの卵摘みスレッドが作成されます下図のように「リアン」「リアンの妻」と呼ばれ、実行結果が同期され、多対多の生産と消費が実現されます。注意すべき点は次のとおりです。

  1. 鶏舎で最大の卵は10個です。
  2. 卵の数が10以上かどうかを判断するproEggs()メソッドでは、whileループが使用されます。Waitは通知を受信し、現在のスレッドをウェイクアップします。ロジックの問題を回避するために、再度判断する必要があります。プログラム。ここでは使用できません。ifを使用すると、プログラムEggsListに10個を超える卵が含まれる場合があります。このプログラムではエラーが発生しやすい場所であり、よく聞かれる点でもあり、調査する価値があります。
  3. 多対多の生産者と消費者。

image.png

ロック条件キュー


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();
    }
}

上記のコードのように、同期をロックに置き換えるだけです。プログラム操作の結果は上記と同じです。ロックと同期の類似点と相違点を以下で比較してみましょう。この質問はインタビュー中によく聞かれます!

 

ロックと同期の比較


ロックと同期の両方で、複数のスレッドを同期できます。主な類似点と相違点は次のとおりです。

  1. ロックの性質:ロックの楽観的ロックは非ブロッキングであり、最下層はcas + volatileによって実装され、同期された悲観的ロックはブロッキングであり、コンテキストの切り替えが必要です。別の方法でアイデアを実現します。
  2. 機能の詳細:ロックは手動でロック解除する必要があり、同期は自動的にロック解除されます。Lockは、tryLockなどのより詳細な機能も提供します。
  3. スレッド通信:ロックは条件キューを提供し、1つのロックは複数の条件キューに対応でき、スレッド制御はより繊細です。同期は、1つの待機/通知にのみ対応できます。

同期、揮発性、およびcasのキーワードについてよく知らない場合は、以前の記事で詳細なケースと説明を読むことができます。

 

総括する


今日、私は実際の例を使用してコードに変換し、2つのマルチスレッドコンシューマー/プロデューサーモードを実現しました。コードをもう一度入力することをお勧めします。コードを注意深く実行すれば、理解できるはずです。気に入ったら、「いいね」をクリックしてフォローしてください。私の名前はリアン[公式アカウント]です。電話して練習します。

image.png

おすすめ

転載: blog.csdn.net/duyabc/article/details/112002870