1. 程式人生 > >java生產者與消費者模式

java生產者與消費者模式

食物 會有 實現 條件 3.2 釋放 tac lee trace

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

目錄

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

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

聲明:本例來源於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 meal1
waitPerson got:Meal1
Chef product meal2
waitPerson got:Meal2
Chef product meal3
waitPerson got:Meal3
Chef product meal4
waitPerson got:Meal4
Chef product meal5
waitPerson got:Meal5
Chef product meal6
waitPerson got:Meal6
Chef product meal7
waitPerson got:Meal7
Chef product meal8
waitPerson got:Meal8
Chef product meal9
waitPerson got:Meal9
out of food,closing
Chef product meal10
Chef interruped
waitPerson 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生產者與消費者模式