zoukankan      html  css  js  c++  java
  • 母鸡下蛋实例:多线程通信生产者和消费者wait/notify和condition/await/signal条件队列

    简介


    多线程通信一直是高频面试考点,有些面试官可能要求现场手写生产者/消费者代码来考察多线程的功底,今天我们以实际生活中母鸡下蛋案例用代码剖析下实现过程。母鸡在鸡窝下蛋了,叫练从鸡窝里把鸡蛋拿出来这个过程,母鸡在鸡窝下蛋,是生产者,叫练捡出鸡蛋,叫练是消费者,一进一出就是线程中的生产者和消费者模型了,鸡窝是放鸡蛋容器。现实中还有很多这样的案例,如医院叫号。下面我们画个图表示下。
    image.png

    一对一生产和消费:一只母鸡和叫练


    wait/notify

    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类表示人。在主函数中创建母鸡对象“小黑”,人对象“叫练”, 创建两个线程分别执行下蛋和捡蛋的过程。代码中定义鸡窝中最多只能装一个鸡蛋(当然可以定义多个)。详细过程:“小黑”母鸡线程和“叫练”线程线程竞争锁,如果“小黑”母鸡线程先获取锁,发现EggsList鸡蛋的个数大于0,表示有鸡蛋,那就调用wait等待并释放锁给“叫练”线程,如果没有鸡蛋,就调用EggsList.LIST.add("1")表示生产了一个鸡蛋并通知“叫练”来取鸡蛋并释放锁让“叫练”线程获取锁。“叫练”线程调用getEggs()方法获取锁后发现,如果鸡窝中并没有鸡蛋就调用wait等待并释放锁通知“小黑”线程获取锁去下蛋,如果有鸡蛋,说明“小黑”已经下蛋了,就把鸡蛋取走,因为鸡窝没有鸡蛋了,所以最后也要通知调用notify()方法通知“小黑”去下蛋,我们观察程序的执行结果如下图。两个线程是死循环程序会一直执行下去,下蛋和捡蛋的过程中用到的锁的是EggsList类的class,“小黑”和“叫练”竞争的都是统一把锁,所以这个是同步的。这就是母鸡“小黑”和“叫练”沟通的过程。
    image.png
    神马???鸡和人能沟通!!
    image.png

    Lock条件队列

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

    如上面代码,只是将synchronized换成了Lock,程序运行的结果和上面的一致,wait/notify换成了AQS的条件队列Condition来控制线程之间的通信。Lock需要手动加锁lock.lock(),解锁lock.unlock()的步骤放在finally代码块保证锁始终能被释放。await底层是unsafe.park(false,0)调用C++代码实现。

    多对多生产和消费:2只母鸡和叫练/叫练媳妇


    wait/notifyAll

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

    如上面代码,参照一对一生产和消费中wait/notify代码做了一些修改,创建了两个母鸡线程“小黑”,“小黄”,两个捡鸡蛋的线程“叫练”,“叫练媳妇”,执行结果是同步的,实现了多对多的生产和消费,如下图所示。有如下几点需要注意的地方:

    1. 鸡窝中能容纳最大的鸡蛋是10个。
    2. 下蛋proEggs()方法中判断鸡蛋数量是否大于等于10个使用的是while循环,wait收到通知,唤醒当前线程,需要重新判断一次,避免程序出现逻辑问题,这里不能用if,如果用if,程序可能出现EggsList有超过10以上鸡蛋的情况。这是这道程序中容易出现错误的地方,也是经常会被问到的点,值得重点探究下。
    3. 多对多的生产者和消费者。

    image.png

    Lock条件队列

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

    如上面代码,只是将synchronized换成了Lock,程序运行的结果和上面的一致,下面我们比较下Lock和synchronized的异同。这个问题也是面试中会经常问到的!

    Lock和synchronized比较


    Lock和synchronized都能让多线程同步。主要异同点表现如下!

    1. 锁性质:Lock乐观锁是非阻塞的,底层是依赖cas+volatile实现,synchronized悲观锁是阻塞的,需要上下文切换。实现思想不一样。
    2. 功能细节上:Lock需要手动加解锁,synchronized自动加解锁。Lock还提供颗粒度更细的功能,比如tryLock等。
    3. 线程通信:Lock提供Condition条件队列,一把锁可以对应多个条件队列,对线程控制更细腻。synchronized只能对应一个wait/notify。

    主要就这些吧,如果对synchronized,volatile,cas关键字不太了解的童鞋,可以看看我之前的文章,有很详细的案例和说明。

    总结


    今天用生活中的例子转化成代码,实现了两种多线程中消费者/生产者模式,给您的建议就是需要把代码敲一遍,如果认真执行了一遍代码应该能看明白,喜欢的请点赞加关注哦。我是叫练【公众号】,边叫边练。
    image.png

  • 相关阅读:
    动态分配内存与静态分配内存
    指针的指针
    cpp与其他语言相比较
    数组是什么
    cocos2d-x 2.1.4 项目配置过程
    显示隐藏文件 osx 10.10
    Windows 10 SDK 10.0.10158
    Office 2016 (Preview)
    Windows 10 SDK 10.0.10069 : The installer failed. User cancelled installation. Error code: -2147023294
    用系统工具sxstrace检查缺少的VC运行时组件
  • 原文地址:https://www.cnblogs.com/jiaolian/p/14214842.html
Copyright © 2011-2022 走看看