多线程通讯一直是高频面试考点,有些面试官可能要求现场手写生产者/消费者代码来考察多线程的功底,今天咱们以实际生活中母鸡下蛋案例用代码剖析下实现过程。母鸡在鸡窝下蛋了,叫练从鸡窝里把鸡蛋拿出来这个过程,母鸡在鸡窝下蛋,是生产者,叫练捡出鸡蛋,叫练是消费者,一进一出就是线程中的生产者和消费者模型了,鸡窝是放鸡蛋容器。现实中还有不少这样的案例,如医院叫号。下面咱们画个图表示下。java
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,“小黑”和“叫练”竞争的都是统一把锁,因此这个是同步的。这就是母鸡“小黑”和“叫练”沟通的过程。
神马???鸡和人能沟通!!面试
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++代码实现。多线程
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代码作了一些修改,建立了两个母鸡线程“小黑”,“小黄”,两个捡鸡蛋的线程“叫练”,“叫练媳妇”,执行结果是同步的,实现了多对多的生产和消费,以下图所示。有以下几点须要注意的地方:ide
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都能让多线程同步。主要异同点表现以下!this
主要就这些吧,若是对synchronized,volatile,cas关键字不太了解的童鞋,能够看看我以前的文章,有很详细的案例和说明。线程
今天用生活中的例子转化成代码,实现了两种多线程中消费者/生产者模式,给您的建议就是须要把代码敲一遍,若是认真执行了一遍代码应该能看明白,喜欢的请点赞加关注哦。我是叫练【公众号】,边叫边练。code