多線程通訊一直是高頻面試考點,有些面試官可能要求現場手寫生產者/消費者代碼來考察多線程的功底,今天咱們以實際生活中母雞下蛋案例用代碼剖析下實現過程。母雞在雞窩下蛋了,叫練從雞窩裏把雞蛋拿出來這個過程,母雞在雞窩下蛋,是生產者,叫練撿出雞蛋,叫練是消費者,一進一出就是線程中的生產者和消費者模型了,雞窩是放雞蛋容器。現實中還有不少這樣的案例,如醫院叫號。下面咱們畫個圖表示下。
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代碼作了一些修改,建立了兩個母雞線程「小黑」,「小黃」,兩個撿雞蛋的線程「叫練」,「叫練媳婦」,執行結果是同步的,實現了多對多的生產和消費,以下圖所示。有以下幾點須要注意的地方:函數
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的異同。這個問題也是面試中會常常問到的!this
Lock和synchronized都能讓多線程同步。主要異同點表現以下!線程
主要就這些吧,若是對synchronized,volatile,cas關鍵字不太瞭解的童鞋,能夠看看我以前的文章,有很詳細的案例和說明。3d
今天用生活中的例子轉化成代碼,實現了兩種多線程中消費者/生產者模式,給您的建議就是須要把代碼敲一遍,若是認真執行了一遍代碼應該能看明白,喜歡的請點贊加關注哦。我是叫練【公衆號】,邊叫邊練。
code