Java 生產者消費者模式詳細分析

来源:https://www.cnblogs.com/f-ck-need-u/archive/2018/01/06/8207371.html
-Advertisement-
Play Games

本文目錄:1.等待、喚醒機制的原理2.Lock和Condition3.單生產者單消費者模式4.使用Lock和Condition實現單生產單消費模式5.多生產多消費模式(單麵包)6.多生產多消費模式 生產者消費者模式是多線程中最為常見的模式:生產者線程(一個或多個)生成麵包放進籃子里(集合或數組),同 ...


本文目錄:
1.等待、喚醒機制的原理
2.Lock和Condition
3.單生產者單消費者模式
4.使用Lock和Condition實現單生產單消費模式
5.多生產多消費模式(單麵包)
6.多生產多消費模式


生產者消費者模式是多線程中最為常見的模式:生產者線程(一個或多個)生成麵包放進籃子里(集合或數組),同時,消費者線程(一個或多個)從籃子里(集合或數組)取出麵包消耗。雖然它們任務不同,但處理的資源是相同的,這體現的是一種線程間通信方式。

本文將先說明單生產者單消費者的情況,之後再說明多生產者多消費者模式的情況。還會分別使用wait()/nofity()/nofityAll()機制、lock()/unlock()機制實現這兩種模式。

在開始介紹模式之前,先解釋下wait()、notify()和notifyAll()方法的用法細節以及改進的lock()/unlock()、await()/signal()/signalAll()的用法。

1.等待、喚醒機制的原理

wait()、notify()和notifyAll()分別表示讓線程進入睡眠、喚醒睡眠線程以及喚醒所有睡眠的線程。但是,對象是哪個線程呢?另外,在API文檔中描述這三個方法都必須在有效監視器(可理解為持有鎖)的前提下使用。這三個方法和鎖有什麼關係呢?

以同步代碼塊synchronized(obj){}或同步函數為例,在它們的代碼結構中可以使用wait()、notify()以及notifyAll(),因為它們都持有鎖。

對於下麵的兩個同步代碼塊來說,分別使用的是鎖obj1和鎖obj2,其中線程1、線程2執行的是obj1對應的同步代碼,線程3、線程4執行的是obj2對應的同步代碼。

class MyLock implements Runnable {
    public int flag = 0;
    Object obj1 = new Object();
    Object obj2 = new Object();
    public void run(){
        while(true){
            if(flag%2=0){
                synchronized(obj1){     //線程t1和t2執行此同步任務
                    //try{obj1.wait();}catch(InterruptedException i){}
                    //obj1.notify()
                    //obj1.notifyAll()
                }
            } else {
                synchronized(obj2){   //線程t3和t4執行此同步任務
                    //try{obj2.wait();}catch(InterruptedException i){}
                    //obj2.notify()
                    //obj2.notifyAll()
                }
            }
        }
    }
}
class Demo {
    public static void main(String[] args){
        MyLock ml = new MyLock();
        Thread t1 = new Thread(ml);
        Thread t2 = new Thread(ml);
        Thread t3 = new Thread(ml);
        Thread t4 = new Thread(ml);
        t1.start();
        t2.start();
        try{Thread.sleep(1)}catch(InterruptedException i){};
        ml.flag++;
        t3.start();
        t4.start();
    }
}

當t1開始執行到wait()時,它將進入睡眠狀態,但卻不是一般的睡眠,而是在一個被obj1標識的線程池中睡眠(實際上是監視器對應線程池,只不過此時的監視器和鎖是綁定在一起的)。當t2開始執行,它發現鎖obj1被其他線程持有,它將進入睡眠態,這次睡眠是因為鎖資源等待而非wait()進入的睡眠。因為t2已經判斷過它要申請的是obj1鎖,因此它也會進入obj1這個線程池睡眠,而不是普通的睡眠。同理t3和t4,這兩個線程會進入obj2線程池睡眠。

當某個線程執行到notify()時,這個notify() 隨機 喚醒它 所屬鎖對應線程池 中的 任意一個 線程。例如,obj1.notify()將喚醒obj1線程池中任意一個睡眠的線程(當然,如果沒有睡眠線程則什麼也不做)。同理notifyAll()則是喚醒所屬鎖對應線程池中所有睡眠的線程。

必須要搞清楚的是"對應鎖",因為在調用wait()、notify()和notifyAll()時都必須明確指定鎖。例如,obj1.wait()。如果省略了所屬鎖,則表示的是this這個對象,也就是說,只有在非靜態的同步函數中才能省略這三個方法的首碼。

簡而言之,當使用了同步,就使用了鎖,線程也就有了歸屬,它的所有依據都由所屬鎖來決定。例如,線程同步時,判斷鎖是否空閑以決定是否執行後面的代碼,亦決定是否去特定的線程池中睡眠,當喚醒時也只會喚醒所屬鎖對應線程池中的線程。

這幾個方法在應用上,一般在一次任務中,wait()notify()/notifyAll()成對出現且擇一執行的。換句話說,就是這一輪原子性同步執行過程中,要麼執行wait()進入睡眠,要麼執行notify()喚醒線程池中的睡眠線程。要如何實現擇一執行,可以考慮使用標記的方式來作為判斷依據。參考後文的例子。

2.Lock和Condition

wait()系列的三個方法局限性很大,因為無論是睡眠還是喚醒的動作,都完全和鎖耦合在一起了。例如,鎖obj1關聯的線程只能喚醒obj1線程池中的線程,而無法喚醒鎖obj2關聯的線程;再例如,在原來synchronized同步時,鎖是在開始同步時隱式地自動獲取的,且是在執行完一整個任務後,又隱式地自動釋放鎖,也就是說獲取鎖和釋放鎖的動作無法人為控制。

從JDK 1.5開始,java提供了java.util.concurrent.locks包,這個包中提供了Lock介面、Condition介面和ReadWriteLock介面,前兩個介面將鎖和監視器方法(睡眠、喚醒操作)解耦了。其中Lock介面只提供鎖,通過鎖方法newConditon()可以生成一個或多個與該鎖關聯的監視器,每個監視器都有自己的睡眠、喚醒方法。也就是說Lock替代了synchronized方法和同步代碼塊的使用,Condition替代了Object監視器方法的使用。

如下圖:

當某線程執行condition1.await()時,該線程將進入condition1監視器對應的線程池睡眠,當執行condition1.signal()時,將隨機喚醒condition1線程池中的任意一個線程,當執行condition1.signalAll()時,將喚醒condition1線程池中的所有線程。同理,對於condition2監視器也是一樣的。

即使有多個監視器,但只要它們關聯的是同一個鎖對象,就可以跨監視器操作對方線程。例如condition1中的線程可以執行condition2.signal()來喚醒condition2線程池中的某個線程。

要使用這種鎖、監視器的關聯方式,參考如下步驟:

import java.util.concurrent.locks.*;
Lock l = new ReentrantLock();
Condition con1 = l.newCondition();
condition con2 = l.newCondition();

l.lock();
try{
    //包含await()、signal()或signalAll()的代碼段...
} finally {
    l.unlock();    //由於代碼段可能異常,但unlock()是必須執行的,所以必須使用try,且將unlock()放進finally段
}

具體用法見後文關於Lock、condition的示例代碼。

3.單生產者單消費者模式

一個生產者線程,一個消費者線程,生產者每生產一個麵包放進盤子里,消費者從盤子里取出麵包進行消費。其中生產者判斷是否繼續生產的依據是盤子里沒有麵包,而消費者判斷是否消費的依據是盤子里有麵包。由於這個模式中,盤子一直只放一個麵包,因此可以把盤子省略掉,生產者和消費者直接手把手地交遞麵包即可。

首先需要描述這三個類,一是多線程共同操作的資源(此處即麵包),二是生產者,三是消費者。在下麵的例子中,我把生產麵包和消費麵包的方法分別封裝到了生產者和消費者類中,如果把它們封裝在麵包類中則更容易理解。

//描述資源:麵包的名稱和編號,由編號決定麵包的號碼
class Bread {
    public String name;
    public int count = 1;

    public boolean flag = false;   //該標記為wait()和notify()提供判斷標記
}

//生產者和消費者先後處理的麵包資源是同一個,要確保這一點,
//可以按單例模式來設計麵包類,也可以將同一個麵包對象通過構造方法傳遞給生產者和消費者,此處使用後一種方式。

//描述生產者
class Producer implements Runnable {
    private Bread b;   //生產者的成員:它要處理的資源

    Producer(Bread b){
        this.b = b;
    }

    //提供生產麵包的方法
    public void produce(String name){
        b.name = name + b.count;
        b.count++;
    }

    public void run(){
        while(true){
            synchronized(Bread.class){   //使用Bread.class作為鎖標識,使得生產者和消費者的同步代碼塊可以使用同一個鎖
                if(b.flag){              //wait()必須在同步代碼塊內部,不僅因為必須持有鎖才能睡眠,而且對鎖這個資源的判斷會出現混亂
                    try{Bread.class.wait();}catch(InterruptedException i){}
                }
                produce("麵包");
                System.out.println(Thread.currentThread().getName()+"----生產者------"+b.name);
                try{Thread.sleep(10);}catch(InterruptedException i){}
                b.flag = true;             //標記的切換也必須在保持同步
                Bread.class.notify();      //notify()也必須同步,否則鎖都已經釋放了,就無法做喚醒動作
                //ps:一次同步任務中,wait()和notify()應當只能其中一個執行,否則對方線程會混亂
            }
        }
    }
}

//描述消費者
class Consumer implements Runnable {
    private Bread b;   //消費者的成員:它要處理的資源

    Consumer(Bread b){
        this.b = b;
    }

    //提供消費麵包的方法
    public String consume(){
        return b.name;
    }

    public void run(){
        while(true){
            synchronized(Bread.class){
                if(!b.flag){
                    try{Bread.class.wait();}catch(InterruptedException i){}
                }
                System.out.println(Thread.currentThread().getName()+"----消費者-------------"+consume());
                try{Thread.sleep(10);}catch(InterruptedException i){}
                b.flag = false;
                Bread.class.notify();
            }
        }
    }
}

public class ProduceConsume_1{
    public static void main(String[] args) {
        //1.創建資源對象
        Bread b = new Bread();

        //2.創建生產者和消費者對象,將同一個麵包對象傳遞給生產者和消費者
        Producer pro = new Producer(b);
        Consumer con = new Consumer(b);

        //3.創建線程對象
        Thread pro_t = new Thread(pro);
        Thread con_t = new Thread(con);

        pro_t.start();
        con_t.start();
    }
}

最後的執行結果應當生產一個、消費一個,如此不斷迴圈。如下:

Thread-0----生產者------麵包1
Thread-1----消費者-------------麵包1
Thread-0----生產者------麵包2
Thread-1----消費者-------------麵包2
Thread-0----生產者------麵包3
Thread-1----消費者-------------麵包3
Thread-0----生產者------麵包4
Thread-1----消費者-------------麵包4
Thread-0----生產者------麵包5
Thread-1----消費者-------------麵包5
Thread-0----生產者------麵包6
Thread-1----消費者-------------麵包6

4.使用Lock和Condition實現單生產單消費模式

代碼如下:

import java.util.concurrent.locks.*;

class Bread {
    public String name;
    public int count = 1;

    public boolean flag = false;

    //為生產者和消費者提供同一個鎖對象以及同一個Condition對象
    public static Lock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();

}

class Producer implements Runnable {
    private Bread b;

    Producer(Bread b){
        this.b = b;
    }

    public void produce(String name){
        b.name = name + b.count;
        b.count++;
    }

    public void run(){
        while(true){
            //使用Bread.lock來鎖住資源
            Bread.lock.lock();   
            try{
                if(b.flag){
                    try{Bread.condition.await();}catch(InterruptedException i){}
                }
                produce("麵包");
                System.out.println(Thread.currentThread().getName()+"----生產者------"+b.name);
                try{Thread.sleep(10);}catch(InterruptedException i){}
                b.flag = true;  
                Bread.condition.signal(); 
            } finally {
                Bread.lock.unlock();
            }
        }
    }
}

class Consumer implements Runnable {
    private Bread b;   

    Consumer(Bread b){
        this.b = b;
    }

    public String consume(){
        return b.name;
    }

    public void run(){
        while(true){
            //使用Bread.lock來鎖住資源
            Bread.lock.lock();
            try{
                if(!b.flag){
                    try{Bread.condition.await();}catch(InterruptedException i){}
                }
                System.out.println(Thread.currentThread().getName()+"----消費者-------------"+consume());
                try{Thread.sleep(10);}catch(InterruptedException i){}
                b.flag = false;
                Bread.condition.signal();
            } finally {
                Bread.lock.unlock();
            }
        }
    }
}

public class ProduceConsume_1{
    public static void main(String[] args) {
        //1.創建資源對象
        Bread b = new Bread();

        //2.創建生產者和消費者對象,將同一個麵包對象傳遞給生產者和消費者
        Producer pro = new Producer(b);
        Consumer con = new Consumer(b);

        //3.創建線程對象
        Thread pro_t = new Thread(pro);
        Thread con_t = new Thread(con);

        pro_t.start();
        con_t.start();
    }
}

5.多生產多消費模式(單麵包)

這裡先說明多生產者多消費者,但同一個時刻最多只能有一個麵包的模式,這個模式在實際中可能是不理想的,但為了引出後面真實的多生產多消費模式,我覺得有必要在這裡解釋這種模式,並且分析這種模式以及如何從單生產單消費的代碼演變而來。

如下圖:

從單生產單消費到多生產多消費,因為多線程安全問題和死鎖問題,所以有兩個方面的問題需要考慮:

  1. 對於某一方來說,如何讓多線程達到和單線程同樣的生產或消費能力?也就是說,如何讓多線程看上去就是單線程。多線程和單線程最大的區別在於多線程安全問題,因此,只要保證多線程執行的任務能夠同步即可。
  2. 第1個問題考慮的是某一方多線程的問題,第2個問題考慮的是兩方如何能和諧配合完成生產消費問題。也就是如何保證生產方和消費方一方活動的同時另一方睡眠。只需在某一方執行完同步任務時,喚醒另一方即可。

其實從單線程到多線程,就兩個問題需要考慮:不同步和死鎖。(1)當生產方和消費方都出現了多線程,可以將生產方的多線程看成一個線程整體、消費方的多線程也看成一個整體,這解決的是線程安全問題。(2)再將生產方整體和消費方整體兩方結合起來看成多線程,來解決死鎖問題,而java中解決死鎖的方式就是喚醒對方或喚醒所有。

問題是如何保證某一方的多線程之間同步?以多線程執行單消費方的代碼為例進行分析。

while(true){
    synchronized(Bread.class){
        if(!b.flag){
            try{Bread.class.wait();}catch(InterruptedException i){}
        }
        System.out.println(Thread.currentThread().getName()+"----消費者-------------"+consume());
        try{Thread.sleep(10);}catch(InterruptedException i){}
        b.flag = false;
        Bread.class.notify();
    }
}

假設消費線程1消費完一個麵包後喚醒了消費線程2,並繼續迴圈,判斷if(!flag),它將wait,於是鎖被釋放。假設CPU正好選中了消費線程2,那麼消費線程2也將進入wait。當生產方生產了一個麵包後,假設喚醒了消費線程1,它將從wait語句處繼續向下消費剛生產完的麵包,假設正好再次喚醒了消費線程2,當消費線程2被CPU選中後,消費線程2也將從wait語句處向下消費,消費的也是剛纔生產的麵包,問題再此出現了,連續喚醒的消費線程1和2消費的是同一個麵包,也就是說麵包被重覆消費了。這又是多線程不同步問題。

說了一大段,其實將視線放大後分析就很簡單了,只要某一方的2個或多個線程都因為判斷b.flag而wait,那麼這兩個或多個線程有可能會被連續喚醒而繼續向下生產或消費。這造成了多線程不同步問題。

不安全的問題就出在同一方的多個線程在連續喚醒後繼續向下生產或消費。這是if語句引起的,如果能夠讓wait的線程在喚醒後還回頭判斷b.flag是否為true,就能讓其決定是否繼續wait還是向下生產或消費。

可以將if語句替換為while語句來滿足要求。這樣一來,無論某一方的多個線程是否被連續喚醒,它們都將回頭判斷b.flag。

while(true){
    synchronized(Bread.class){
        while(!b.flag){
            try{Bread.class.wait();}catch(InterruptedException i){}
        }
        System.out.println(Thread.currentThread().getName()+"----消費者-------------"+consume());
        try{Thread.sleep(10);}catch(InterruptedException i){}
        b.flag = false;
        Bread.class.notify();
    }
}

解決了第一個多線程安全的問題,但會出現死鎖問題。這很容易分析,將生產方看作一個整體,將消費方也看作一個整體,當生產方線程都wait了(生產方的線程被連續喚醒時會出現該方線程全部wait),消費方也都wait了,死鎖就出現了。其實放大了看,將生產方、消費方分別看作一個線程,這兩個線程組成多線程,當某一方wait後無法喚醒另一方,另一方也一定會wait,於是就死鎖了。

對於雙方死鎖的問題,只要保證能喚醒對方,而非本方連續喚醒就能解決。使用notifyAll()或signalAll()即可,也可以通過signal()喚醒對方線程解決,見下麵的第二段代碼。

根據上面的分析,將單生產、單消費模式的代碼改進一下,就可以變為多生產多消費單麵包模式。、

//代碼段1
class Bread {
    public String name;
    public int count = 1;

    public boolean flag = false;   
}

//描述生產者
class Producer implements Runnable {
    private Bread b;   

    Producer(Bread b){
        this.b = b;
    }

    public void produce(String name){
        b.name = name + b.count;
        b.count++;
    }

    public void run(){
        while(true){
                synchronized(Bread.class){
                    while(b.flag){ 
                        try{Bread.class.wait();}catch(InterruptedException i){}
                    }
                    produce("麵包");
                    System.out.println(Thread.currentThread().getName()+"----生產者------"+b.name);
                    try{Thread.sleep(10);}catch(InterruptedException i){}
                    b.flag = true; 
                    Bread.class.notifyAll();
            }
        }
    }
}

//描述消費者
class Consumer implements Runnable {
    private Bread b;

    Consumer(Bread b){
        this.b = b;
    }

    public String consume(){
        return b.name;
    }

    public void run(){
        while(true){
                synchronized(Bread.class){
                    while(!b.flag){
                        try{Bread.class.wait();}catch(InterruptedException i){}
                    }
                    System.out.println(Thread.currentThread().getName()+"----消費者-------------"+consume());
                    try{Thread.sleep(10);}catch(InterruptedException i){}
                    b.flag = false;
                    Bread.class.notifyAll();
                }
        }
    }
}

public class ProduceConsume_5 {
    public static void main(String[] args) {
        //1.創建資源對象
        Bread b = new Bread();

        //2.創建生產者和消費者對象
        Producer pro = new Producer(b);
        Consumer con = new Consumer(b);

        //3.創建線程對象
        Thread pro_t1 = new Thread(pro);  //生產線程1
        Thread pro_t2 = new Thread(pro);  //生產線程2
        Thread con_t1 = new Thread(con);  //消費線程1
        Thread con_t2 = new Thread(con);  //消費線程2

        pro_t1.start();
        pro_t2.start();
        con_t1.start();
        con_t2.start();
    }
}

以下是採用Lock和Conditon重構後的代碼,使用的是signal()喚醒對方線程的方法。

//代碼段2
import java.util.concurrent.locks.*;

class Bread {
    public String name;
    public int count = 1;

    public boolean flag = false;

    public static Lock lock = new ReentrantLock();
    public static Condition pro_con = lock.newCondition();
    public static Condition con_con = lock.newCondition();
}

//描述生產者
class Producer implements Runnable {
    private Bread b;

    Producer(Bread b){
        this.b = b;
    }

    public void produce(String name){
        b.name = name + b.count;
        b.count++;
    }

    public void run(){
        while(true){
            Bread.lock.lock();
            try{
                while(b.flag){
                    try{Bread.pro_con.await();}catch(InterruptedException i){}
                }
                produce("麵包");
                System.out.println(Thread.currentThread().getName()+"----生產者------"+b.name);
                try{Thread.sleep(10);}catch(InterruptedException i){}
                b.flag = true;
                Bread.con_con.signal();   //喚醒的是consumer線程
            } finally {
                Bread.lock.unlock();
            }
        }
    }
}

//描述消費者
class Consumer implements Runnable {
    private Bread b;

    Consumer(Bread b){
        this.b = b;
    }

    public String consume(){
        return b.name;
    }

    public void run(){
        while(true){
            Bread.lock.lock();
            try{
                while(!b.flag){
                    try{Bread.con_con.await();}catch(InterruptedException i){}
                }
                System.out.println(Thread.currentThread().getName()+"----消費者-------------"+consume());
                try{Thread.sleep(10);}catch(InterruptedException i){}
                b.flag = false;
                Bread.pro_con.signal();     //喚醒的是producer線程
            } finally {
                Bread.lock.unlock();
            }
        }
    }
}

public class ProduceConsume_6 {
    public static void main(String[] args) {
        //1.創建資源對象
        Bread b = new Bread();

        //2.創建生產者和消費者對象
        Producer pro = new Producer(b);
        Consumer con = new Consumer(b);

        //3.創建線程對象
        Thread pro_t1 = new Thread(pro);
        Thread pro_t2 = new Thread(pro);
        Thread con_t1 = new Thread(con);
        Thread con_t2 = new Thread(con);

        pro_t1.start();
        pro_t2.start();
        con_t1.start();
        con_t2.start();
    }
}

關於多生產、多消費問題做個總結:

  • (1).解決某一方多線程不同步的方案是使用while(flag)來判斷是否wait;
  • (2).解決雙方死鎖問題的方案是喚醒對方,可以使用notifyAll(),signalAll()或對方監視器的signal()方法。

6.多生產多消費模式

有多個生產者線程,多個消費者線程,生產者將生產的麵包放進籃子(集合或數組)里,消費者從籃子里取出麵包。生產者判斷繼續生產的依據是籃子已經滿了,消費者判斷繼續消費的依據是籃子是否空了。此外,當消費者取出麵包後,對應的位置又空了,生產者可以回頭從籃子的起始位置繼續生產,這可以通過重置籃子的指針來實現。

在這個模式里,除了描述生產者、消費者、麵包,還需要描述籃子這個容器。假設使用數組作為容器,生產者每生產一個,生產指針向後移位,消費者每消費一個,消費指針向後移位。

代碼如下:可參考API-->Condition類中給出的示例代碼

import java.util.concurrent.locks.*;

class Basket {
    private Bread[] arr;

    //the size of basket
    Basket(int size){
        arr = new Bread[size];
    }
    //the pointer of in and out
    private int in_ptr,out_ptr;

    //how many breads left in basket
    private int left;

    private Lock lock = new ReentrantLock();
    private Condition full = lock.newCondition();
    private Condition empty = lock.newCondition();

    //bread into basket
    public void in(){
        lock.lock();
        try{
            while(left == arr.length){
                try{full.await();} catch (InterruptedException i) {i.printStackTrace();}
            }
            arr[in_ptr] = new Bread("MianBao",Producer.num++);
            System.out.println("Put the bread: "+arr[in_ptr].getName()+"------into basket["+in_ptr+"]");
            left++;
            if(++in_ptr == arr.length){in_ptr = 0;}
            empty.signal();
        } finally {
            lock.unlock();
        }
    }

    //bread out from basket
    public Bread out(){
        lock.lock();
        try{
            while(left == 0){
                try{empty.await();} catch (InterruptedException i) {i.printStackTrace();}
            }
            Bread out_bread = arr[out_ptr];
            System.out.println("Get the bread: "+out_bread.getName()+"-----------from basket["+out_ptr+"]");
            left--;
            if(++out_ptr == arr.length){out_ptr = 0;}
            full.signal();
            return out_bread;
        } finally {
            lock.unlock();
        }
    }
}

class Bread {
    private String name;

    Bread(String name,int num){
        this.name = name + num;
    }
    public String getName(){
        return this.name;
    }
}

class Producer implements Runnable {
    private Basket basket;
    public static int num = 1;  //the first number for Bread's name

    Producer(Basket b){
        this.basket = b;
    }

    public void run(){
        while(true) {
            basket.in();
            try{Thread.sleep(10);}catch(InterruptedException i){}
        }
    }
}

class Consumer implements Runnable {
    private Basket basket;
    private Bread i_get;

    Consumer(Basket b){
        this.basket = b;
    }

    public void run(){
        while(true){
            i_get = basket.out();
            try{Thread.sleep(10);}catch(InterruptedException i){}
        }
    }
}

public class ProduceConsume_7 {
    public static void main(String[] args) {
        Basket b = new Basket(20);  // the basket size = 20

        Producer pro = new Producer(b);
        Consumer con = new Consumer(b);

        Thread pro_t1 = new Thread(pro);
        Thread pro_t2 = new Thread(pro);
        Thread con_t1 = new Thread(con);
        Thread con_t2 = new Thread(con);
        Thread con_t3 = new Thread(con);

        pro_t1.start();
        pro_t2.start();
        con_t1.start();
        con_t2.start();
        con_t3.start();
    }
}

這裡涉及了消費者、生產者、麵包和籃子,其中麵包和籃子是多線程共同操作的資源,生產者線程生產麵包放進籃子,消費者線程從籃子中取出麵包。理想的代碼是將生產任務和消費任務都封裝在資源類中,因為麵包是籃子容器的元素,所以不適合封裝到麵包類中,而且封裝到籃子中,能更方便地操作容器。

註意,一定要將所有涉及資源操作的代碼都放進鎖的內部,否則會產生多線程不同步問題。例如,在Producer類中定義了生產麵包的方法produce(),然後將其作為放進籃子的方法basket.in()的參數,即basket.in(producer()),這是錯誤的行為,因為produce()是在鎖的外部執行後才傳遞給in()方法的。

 

註:若您覺得這篇文章還不錯請點擊右下角推薦,您的支持能激發作者更大的寫作熱情,非常感謝!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 有一天突然想到一個問題,web端的許可權控制:1.真的能控制許可權嗎?2.僅僅靠前端,能不能做到真正的許可權控制?3.如果需要後臺配合,應該如何配合?可能這是一個老生常談的問題,但還是想整理下,有誤的地方望大家指出。 何為許可權控制 許可權控制大致分為兩個維度: 垂直維度: 控制用戶可以訪問哪些url的許可權 ...
  • 此文主要探討了 React JS 中的 setState 背後的機制,供深入學習 React 研究之用。 ...
  • 為了實現 “ 數據修改導致視圖發生 “ 我們選擇使用發佈訂閱模式 以下是簡單的發佈訂閱模式的實現 ------------------------------------------------- function Dep() { this.watchers=[]; } Dep.prototype.... ...
  • 1,ie7 ie8中出現橫向下拉框 原因在於:(1)css3的transition,keyframes屬性不支持,使得動畫效果的top left定位的值無法更改,圖片占據空間。從而會出現滾動條。。。 ...
  • 一、get方法 1 package lq.httpclient.method; 2 3 import java.io.BufferedReader; 4 import java.io.IOException; 5 import java.io.InputStreamReader; 6 import ...
  • 1.3.Python基本語法 1.3.1 行和縮進 Python中,不使用括弧來表示代碼的類和函數定義塊或流程式控制制。 代碼塊是由行縮進,縮進位的數目是可變的,但是在塊中的所有語句必須縮進相同的量。 如下所示: if True: print "True"[dht1] else: print "Fals ...
  • c語言貪吃蛇詳解4.食物的投放 前幾天的實驗室培訓課後作業我佈置了貪吃蛇,今天有時間就來寫一下題解。我將分幾步來教大家寫一個貪吃蛇小游戲。由於大家c語言未學完,這個教程只涉及數組和函數等知識點。 通過前幾次的教程,我們已經做出來了能上下左右跑的小蛇了。現在我們就先來做下食物投放吧。 食物投放的基本思 ...
  • 中間件是面向切麵編程的好例子,它是一個可以介入Django的request和response處理過程的鉤子框架,一個輕量級、底層的“插件”系統,用於在全局修改Django的輸入或輸出。 要使用中間件,首先要在settings中設置: 上述是Django項目的預設設置,每一項字元串都代表一個中間件。中 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...