java生產者與消費者模式

來源:互聯網
上載者:User

標籤:對象   http   trace   class   同步   this   運行   而不是   執行個體變數   

前言: 生產者和消費者模式是我們在學習多線程中很經典的一個模式,它主要分為生產者和消費者,分別是兩個線程,

目錄

一:生產者和消費者模式簡介

二:生產者和消費者模式的實現

聲明:本例來源於java經典著作:《Think in java》,接下來將會採用本例子將會借鑒其中的案例進行分析

首先我們來設想有一個這樣的情境:一個飯店裡有一個做飯的廚師和來吃飯的人,服務員負責端食物,這裡就可以把廚師當做生產者,(這裡暫且把服務員當做消費者),而食物則有這樣的過程,被廚師生產出來,然後被服務員消費。當食物存在的時候,廚師等待,不再進行生產,服務員進行消費。當食物為空白的時候,廚師開始生產食物,服務員等待。這中間就存在著一個線程之間協作的過程。我們來通過代碼進行類比:

 

首先是建立兩個線程,一個廚師線程,一個服務員線程,雙方進行協作:

以下是廚師線程:

public class Chef  implements  Runnable{ //product    private  Restaurant restaurant;    private  int count=0;    public Chef(Restaurant restaurant) {        this.restaurant = restaurant;    }    @Override    public void run() {        try {            while (!Thread.interrupted()) {                synchronized (this) {                    while (restaurant.meal != null) {                        wait();                    }                    if (++count==10){                        System.out.println("out of food,closing");                        restaurant.exec.shutdownNow();                    }                    System.out.println("Chef product meal"+count);                    synchronized (restaurant.waitPerson){                       restaurant.meal= new Meal(count);                       restaurant.waitPerson.notifyAll();                    }                    TimeUnit.MICROSECONDS.sleep(100);                }            }        }catch (InterruptedException e){            System.out.println("Chef interruped");        }    }}

 

以下是服務員線程:

public class WaitPerson implements  Runnable{     private  Restaurant restaurant;    public WaitPerson(Restaurant restaurant) {        this.restaurant = restaurant;    }    @Override    public void run() {        try{            while (!Thread.interrupted()) {                synchronized (this) {                    while (restaurant.meal == null) {                        wait();                    }                    System.out.println("waitPerson got:"+restaurant.meal);                }                synchronized (restaurant.chef){                    restaurant.meal=null;                    restaurant.chef.notifyAll();                }            }        } catch (InterruptedException e) {            e.printStackTrace();            System.out.println("waitPerson interrupdate");        }    }}

 我們來分析一下廚師線程,主要看它的run方法,裡麵包含著一個try、catch塊,首先它會一直捕獲線程的狀態,當它不處於interrupted(線程中斷,此時無法運轉)異常時,往下走。然後取得當前對象的鎖,把它上鎖,通過一個while迴圈,取得餐館裡面的meal,判斷其是否為null.當餐館裡面的餐還有剩餘的時候,此時生產者不需要工作,它處於wait狀態,注意wait和Thread.sleep的區別,sleep方法啟動並執行時候線程是不會丟棄鎖的,而wait方法會釋放鎖,所以此時,被鎖住的對象運行到wait方法,它已經釋放了鎖,因此消費者可以擷取到鎖。

再接著count會進行累加1。再接著鎖住服務員線程,此時服務員線程擷取鎖的主動權,它通過notifyAll方法喚醒所有的在鎖上等待線程,注意此處為什麼是notifyAll而不是notify,這主要是因為此刻廚師線程並不知道等待的線程究竟是幾條,為了安全執行緒起見,喚醒所有的等待線程。注意的是:notifyAll會喚醒所有線程,但是啟動並執行只是其中一個,關於這個線程的篩選,是完全隨機的。

喚醒了消費者線程,它就會運行到消費者線程,然後我們來看一下它的run方法,和之前的廚師線程差不多,依然是鎖住當前的消費者線程,不過線程此時的條件變成了meal為null。接著再擷取廚師的鎖,使其meal為null,再喚醒廚師線程,接著程式就會運轉到廚師線程,執行廚師的run方法

程式執行的結果:

Chef product meal1waitPerson got:Meal1Chef product meal2waitPerson got:Meal2Chef product meal3waitPerson got:Meal3Chef product meal4waitPerson got:Meal4Chef product meal5waitPerson got:Meal5Chef product meal6waitPerson got:Meal6Chef product meal7waitPerson got:Meal7Chef product meal8waitPerson got:Meal8Chef product meal9waitPerson got:Meal9out of food,closingChef product meal10Chef interrupedwaitPerson interrupdate

可以看到結果,線程有條不紊的運行,生產者每生產一個meal,發出order up訊號,然後消費者消費這個meal,再輪到生產者,再到消費者,這就是生產者和消費者的模式的意義,它們之間同步工作,不會出現消費者線程沒有meal的時候仍然去消費,生產者在有meal的時候依然去生產,這樣就會產生安全執行緒的問題。

 三:關於線程之間的通訊

3.1:wait和notfiy方法

 

 查詢jdk可以看出,wait和notify方法都是object的方法,也就是說java所有的對象都可以繼承這個方法。每個對象啟動並執行時候都可以關聯一個線程,調用wait方法就可以使當前線程處於等待狀態,等待的時候它的它會釋放掉鎖,此時其它對象可以擷取線程鎖。而wait方法和notify是進行相互溝通的,只有notify/notifyAll方法才能喚醒被wait方法等待的線程,notify會喚醒這個線程。

注意看它的解釋:喚醒在此對象監視器上等待的單個/所有線程,那麼問題來了。監視器又是什嗎?

3.2:java監視器

3.2.1:對監視器的解釋

監視器:monitor
鎖:lock(JVM裡只有一種獨佔方式的lock)
進入監視器:entermonitor
離開/釋放監視器:leavemonitor
(entermonitor和leavemonitor是JVM的指令)
擁有者:owner

在JVM裡,monitor就是實現lock的方式
entermonitor就是獲得某個對象的lock(owner是當前線程)
leavemonitor就是釋放某個對象的lock

 

3.2.2:通俗的解釋

      上面的這段話比較精簡,其實理解起來很簡單。有這樣一句話:程式即生活,我們把它代入到生活實際中試衣間來思考一下,其實不難理解這段話。我們去一個商城購買衣服,選擇好了,我們去試衣間,此刻好比對象entermonitor ,執行進入監視器這個指令,我們離開試衣間,就好比執行leavemonitor指令,這個monitor是一個狹小的空間,當一個對象進入的時候,其它對象就不能進入了。這其中的原理實現了隊列等待和競爭的,線程進入會先排隊,如果有線程進入監視器,當前線程就會等待,如果沒有就直接進入,新來的線程會和已經等待的線程進行競爭,但是最後只有一個能進入,進入監視器的線程可以訪問它的所有資料,包括類變數和執行個體變數。這也就實現了鎖的原理。

3.3:線程協作解決的問題

3.3.1:線程資料訪問的紊亂

假如沒有協作的話,資料之間的訪問一定會出現紊亂,比如生產者會可能在有很多meal的時候仍然會生產meal,消費者會在沒有meal的時候依然進行消費,這都是我們在多線程的程式中不願看到的,因為它會有很多不預期的事情發生

3.3.2:資料會錯位

資料之間會錯位,這體現在生產者會消費meal,而消費者會生產meal。此刻他們不能各自履行各自的職責,這也是我們極度不願意看到的,而不採用保障措施就會有這樣問題的出現。

可以看到

四:總結

 本篇部落客要引用《Thinking in java》中的一段進行講解,講了線程之間的通訊與協作的問題,如何合理運用wait和notify方法進行線程之間的通訊,實現一個系統有條不紊的運行。這其中的最關鍵點在於理解鎖的釋放與擷取的過程,以及線程的切換,生產者和消費者的相互溝通,協同工作的原理。

java生產者與消費者模式

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.