線程通訊,在多線程系統中,不一樣的線程執行不一樣的任務;若是這些任務之間存在聯繫,那麼執行這些任務的線程之間就必須可以通訊,共同協調完成系統任務。java
案例分析編程
在案例中明,蔬菜基地做爲生產者,負責生產蔬菜,並向超市輸送生產的蔬菜;消費者經過向超市購買得到蔬菜;超市怎做爲生產者和消費者之間的共享資源,都會和超市有聯繫;蔬菜基地、共享資源、消費者之間的交互流程以下:segmentfault
在這個案例中,爲何不設計成生產者直接與給消費者交互?讓二者直接交換數據不是更好嗎,選擇先先把數據存儲到共享資源中,而後消費者再從共享資源中取出數據使用,中間多了一個環節不是更麻煩了?安全
其實不是的,設計成這樣是有緣由的,由於這樣設計很好的體現了面向對象的低耦合的設計理念;經過這樣實現的程序能更加符合人的操做理念,更加貼合現實環境;同時,也能很好的避免因生產者與消費者直接交互而致使的操做不安全的問題。網絡
咱們來對高耦合和低耦合作一個對比就會很直觀了:多線程
關於高耦合和低耦合的區別,電腦中主機中的集成顯卡和獨立顯卡也是一個很是好的例子。異步
CPU
中,因此若是集成顯卡出現了問題須要更換,那麼會連着CPU
一塊更換,其維護成本與CPU
實際上是同樣的;接下來咱們使用多線程技術實現該案例,案例代碼以下:ide
蔬菜基地對象,VegetableBase.java性能
// VegetableBase.java // 蔬菜基地 public class VegetableBase implements Runnable { // 超市實例 private Supermarket supermarket = null; public VegetableBase(Supermarket supermarket) { this.supermarket = supermarket; } @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0) { supermarket.push("黃瓜", 1300); System.out.println("push : 黃瓜 " + 1300); } else { supermarket.push("青菜", 1400); System.out.println("push : 青菜 " + 1400); } } } }
消費者對象,Consumer.javathis
// Consumer.java // 消費者 public class Consumer implements Runnable { // 超市實例 private Supermarket supermarket = null; public Consumer(Supermarket supermarket) { this.supermarket = supermarket; } @Override public void run() { for (int i = 0; i < 100; i++) { supermarket.popup(); } } }
超市對象,Supermarket.java
// Supermarket.java // 超市 public class Supermarket { // 蔬菜名稱 private String name; // 蔬菜數量 private Integer num; // 蔬菜基地想超市輸送蔬菜 public void push(String name, Integer num) { this.name = name; this.num = num; } // 用戶從超市中購買蔬菜 public void popup() { // 爲了讓效果更明顯,在這裏模擬網絡延遲 try { Thread.sleep(1000); } catch (InterruptedException e) { } System.out.println("蔬菜:" + this.name + ", " + this.num + "顆。"); } }
運行案例,App.java
// 案例應用入口 public class App { public static void main(String[] args) { // 建立超市實例 Supermarket supermarket = new Supermarket(); // 蔬菜基地線程啓動, 開始往超市輸送蔬菜 new Thread(new VegetableBase(supermarket)).start(); new Thread(new VegetableBase(supermarket)).start(); // 消費者線程啓動,消費者開始購買蔬菜 new Thread(new Consumer(supermarket)).start(); new Thread(new Consumer(supermarket)).start(); } }
發現了問題
運行該案例,打印出運行結果,外表一片祥和,可仍是被敏銳的發現了問題,問題以下所示:
在一片看似祥和的打印結果中,出現了一個很不祥和的特例,生產基地在輸送蔬菜時,黃瓜的數量一直都是1300
顆,青菜的數量一直是1400
顆,可是在消費者消費時卻出現了蔬菜名稱是黃瓜的,但數量倒是青菜的數量的狀況。
之因此出現這樣的問題,是由於在本案例共享的資源中,多個線程共同競爭資源時沒有使用同步操做,而是異步操做,今兒致使了資源分配紊亂的狀況;須要注意的是,並非由於咱們在案例中使用Thread.sleep();
模擬網絡延遲才致使問題出現,而是原本就存在問題,使用Thread.sleep();
只是讓問題更加明顯。
在本案例中須要解決的問題有兩個,分別以下:
針對問題一解決方案:保證蔬菜基地在輸送蔬菜的過程保持同步,中間不能被其餘線程(特別是消費者線程)干擾,打亂輸送操做;直至當前線程完成輸送後,其餘線程才能進入操做,一樣的,當有線程進入操做後,其餘線程只能在操做外等待。
因此,技術方案可使用同步代碼塊/同步方法/Lock機制來保持操做的同步性。
針對問題二的解決方案:給超市一個有無貨的狀態標誌,
保證生產基地 ——> 共享資源 ——> 消費者
這個整個流程的完整運行。技術方案:使用線程中的等待和喚醒機制。
同步操做,分爲同步代碼塊和同步方法兩種。詳情可查看個人另一篇關於多線程的文章:「JAVA」Java 線程不安全分析,同步鎖和Lock機制,哪一個解決方案更好
wait
和notify
方法,wait
和notify
方法存在於Object
類中。在 java.lang.Object
類 中提供了用於操做線程通訊的方法,詳情以下:
wait()
:執行該方法的線程對象會釋放同步鎖,而後JVM
把該線程存放到等待池中,等待着其餘線程來喚醒該線程;notify()
:執行該方法的線程會喚醒在等待池中處於等待狀態的的任意一個線程,把線程轉到同步鎖池中等待;notifyAll()
:執行該方法的線程會喚醒在等待池中處於等待狀態的全部的線程,把這些線程轉到同步鎖池中等待;注意:上述方法只能被同步監聽鎖對象來調用,不然發生 IllegalMonitorStateException
。
wait和notify方法應用實例
假設 A線程
和B線程
共同操做一個X對象
(同步鎖) ,A、B線程
能夠經過X對象
的wait
和notify
方法來進行通訊,流程以下:
A線程
執行X對象
的同步方法時,A線程
持有X對象
的鎖,B線程
沒有執行機會,此時的B線程
會在X對象
的鎖池中等待;A線程
在同步方法中執行X.wait()
方法時,A線程
會釋放X對象
的同步鎖,而後進入X對象
的等待池中;X對象
的鎖池中等待鎖的B線程
獲取X對象
的鎖,執行X
的另外一個同步方法;B線程
在同步方法中執行X.notify()
方法時,JVM
會把A線程
從X對象
的等待池中轉到X對象
的同步鎖池中,等待獲取鎖的使用權;B線程
執行完同步方法後,會釋放擁有的鎖,而後A線程
得到鎖,繼續執行同步方法;基於上述機制,咱們就可使用同步操做 + wait和notify方法來解決案例中的問題了,從新來實現共享資源——超市對象:
// 超市 public class Supermarket { // 蔬菜名稱 private String name; // 蔬菜數量 private Integer num; // 超市是否爲空 private Boolean isEmpty = true; // 蔬菜基地向超市輸送蔬菜 public synchronized void push(String name, Integer num) { try { while (!isEmpty) { // 超市有貨時,再也不輸送蔬菜,而是要等待消費者獲取 this.wait(); } this.name = name; this.num = num; isEmpty = false; this.notify(); // 喚醒另外一個線程 } catch(Exception e) { } } // 用戶從超市中購買蔬菜 public synchronized void popup() { try { while (isEmpty) { // 超市無貨時,再也不提供消費,而是要等待蔬菜基地輸送 this.wait(); } // 爲了讓效果更明顯,在這裏模擬網絡延遲 Thread.sleep(1000); System.out.println("蔬菜:" + this.name + ", " + this.num + "顆。"); isEmpty = true; this.notify(); // 喚醒另外一線程 } catch (Exception e) { } } }
因爲wait
和notify
方法,只能被同步監聽鎖對象來調用,不然發生IllegalMonitorStateException
。從Java 5
開始,提供了Lock機制
,同時還有處理Lock機制
的通訊控制的Condition接口
。Lock機制
沒有同步鎖的概念,也就沒有自動獲取鎖和自動釋放鎖的這樣的操做了。
由於沒有同步鎖,因此Lock機制
中的線程通訊就不能調用wait
和notify
方法了;一樣的,Java 5
中也提供瞭解決方案,所以從Java 5
開始,能夠:
Lock機制
取代synchronized
代碼塊和synchronized
方法;Condition接口
對象的await、signal、signalAll
方法取代Object
類中的wait、notify、notifyAll
方法;Lock和Condition接口
的性能也比同步操做要高不少,因此這種方式也是咱們推薦使用的方式。
咱們可使用Lock機制和Condition接口
方法來解決案例中的問題,從新來實現的共享資源——超市對象,代碼以下:
// 超市 public class Supermarket { // 蔬菜名稱 private String name; // 蔬菜數量 private Integer num; // 超市是否爲空 private Boolean isEmpty = true; // lock private final Lock lock = new ReentrantLock(); // Condition private Condition condition = lock.newCondition(); // 蔬菜基地向超市輸送蔬菜 public synchronized void push(String name, Integer num) { lock.lock(); // 獲取鎖 try { while (!isEmpty) { // 超市有貨時,再也不輸送蔬菜,而是要等待消費者獲取 condition.await(); } this.name = name; this.num = num; isEmpty = false; condition.signalAll(); } catch(Exception e) { } finally { lock.unlock(); // 釋放鎖 } } // 用戶從超市中購買蔬菜 public synchronized void popup() { lock.lock(); try { while (isEmpty) { // 超市無貨時,再也不提供消費,而是要等待蔬菜基地輸送 condition.await(); } // 爲了讓效果更明顯,在這裏模擬網絡延遲 Thread.sleep(1000); System.out.println("蔬菜:" + this.name + ", " + this.num + "顆。"); isEmpty = true; condition.signalAll(); } catch (Exception e) { } finally { lock.unlock(); } } }
完結,老夫雖不正經,但老夫一身的才華!關注我,獲取更多編程科技知識。