多線程學習-----從經典模式之生產者消費者模式入手

 生產者消費者模式是一個經典的多線程模式,要搞懂線程的同步異步, 從生產者消費者模式出發是一個很好的學習方式,但是這個模式是面向過程開發的軟件模式, 它並不是面向對象的23種JAVA設計模式。
 關於生產者消費者模式,就是

  • 生產者線程(可以是一個線程生產,也可以是多個線程生產)生成產品放到倉庫裏
  • 消費者線程(可以是一個線程消費,也可以是多個線程消費)從倉庫取出產品消耗

雖然他們的功能(生產、消費)不一樣,但是處理的數據(生產出的產品)是相同的,這樣是牽扯出來一個多線程通訊的問題----------在不同線程間傳遞數據
 有關生產者消費者,有這麼幾種形式:

  1. 單生產者單消費者模式
  2. 多生產者多消費者模式

分別使用synchronize/wait()/notify()/notifyAll()、lock()/unLock()的不同方式去實現,在實現這個模式之前,我們有必要介紹一下線程的五種狀態:

  • 創建
  • 就緒
  • 運行
  • 阻塞
  • 死亡

線程Thread類的等待、喚醒函數

  • wait和notify/notifyAll,與其他函數不同的是,這三個函數是定義在Object類中的,這意味着他們是任何類都共有的"屬性",wait與notify總是成對出現的(等待,需要後期喚醒它,否則直接殺死就好,要喚醒,那麼前提是它處於等待狀態,運行狀態不用喚也非常清醒),wait函數不是任意情形下都能開始等待的,其調用者需要成爲Object的監視器(monitor),wait函數的調用纔是有效的,是需要基於這個object的synchronized同步,才能給調用者這個對象加上monitor,而notify是隨機喚醒這個對象鎖所在線程池中任意的一個睡眠線程(且是不可預知的),notifyAll是喚醒所有(它是可以將所有線程喚醒,但是CPU具體是選擇哪個線程也是不可控的,這裏是喚醒所有但是運行的時候還是隻運行一個線程,不考慮多核CPU移動設備)
  • interrupt,它是一種被動的中斷狀態,有三種狀態:①、如果線程正被blocked正在某個obj的wait、join、sleep中,線程會被喚醒,中斷狀態被清除且收到interrupt異常;②、如果線程正被blocked在interrupttibleChannel的I/O操作,中斷會被置位接收CloseByInterruptException異常;③、如果線程正被blocked在selector中,中斷置位且馬上返回
  • join 翻譯是節點,也就是說join函數可以控制不同線程的執行順序,它能保證T1執行完畢纔去執行T2.start(),帶時間參數的t1.join(long millis),會多一個限制,如果在規定時間內T1沒有完成,那麼時間到了以後T2也會執行
  • sleep 這個就是直接睡着,但是不釋放鎖,CPU會一直阻塞系統並等待它醒來繼續持有CPU執行權,而wait沉睡時,是釋放對象鎖的

 事實上,wait系列的三個函數是有很多缺陷的,睡眠和喚醒與鎖完全耦合在一起,導致obj1鎖同步代碼塊中wait狀態的線程只能由obj1鎖所在線程池notify去喚醒,再一個,在synchronized同步時,鎖是隱式且自動的拿到鎖,當執行完這一任務後,鎖又隱式的自動釋放鎖,這一行爲完全無法人爲控制,
 從JDK 1.5開始,java提供了java.util.concurrent.locks包,這個包裏提供了:①、Lock接口;②、condition接口、ReadWriterLock接口,前兩個接口將鎖和monitor的方法(wait、notify)解耦了,其中Lock只提供鎖,通過鎖方法new condition()可以生成一個或多個與該鎖有關聯的監視器monitor,每個monitor都有自己的wait 、notify方法。也就是說Lock代替了synchronized方法和同步代碼塊的使用,condition代替了object的monitor的方法使用
在這裏插入圖片描述
當某線程執行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()的代碼段...
 //con1 中的線程 可以調用 con2.signal(),從而喚醒con2 所在線程池中await的線程
} finally {
 l.unlock(); //由於代碼段可能異常,但unlock()是必須執行的,所以必須使用try,且將unlock()放進finally段
}

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

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

首先用synchronized同步代碼塊實現

//產品類
package com.my_project.test_more_thread;

/**
 * Created by Administrator on 2019\2\25 0025.
 * 生產者和消費者先後處理的product資源是同一個,要確保這一點,
 * 可以按單例模式來設計product類,也可以將同一個product對象通過構造方法傳遞給生產者和消費者,
 */

public class Products {
    private String name;
    private int count = 1;
    private boolean flag = false;//爲wait和notify提供判斷標記

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}


//生產者---生產工廠
/**
 * Created by Administrator on 2019\2\25 0025.
 */

public class ProductFactory implements Runnable {
    private Products p;

    public ProductFactory(Products product) {
        this.p = product;
    }

    //對外暴露生產產品的方法
    public void startProduct(String name) {
        p.setName(name + p.getCount());
        p.setCount(p.getCount() + 1);
    }

    @Override
    public void run() {
        while (true) {
            synchronized (Products.class) {
                if (p.isFlag()) {
                    //沒有在消費,說明倉庫沒有產品了,生產者不需要睡眠,趕緊醒來工作了
                    try {
                        Products.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                startProduct("漢堡");
                Log.e("ProductFactory", Thread.currentThread().getName() + "----生產者------" + p.getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                p.setFlag(true);
                Products.class.notify();
            }
        }
    }
}


//消費者---顧客
/**
 * Created by Administrator on 2019\2\25 0025.
 */

public class _Customer implements Runnable {

    private Products p;

    public _Customer(Products product) {
        this.p = product;
    }

    //提供消費產品的方法
    public String consume() {
        return p.getName();
    }

    @Override
    public void run() {
        while (true) {
            synchronized (Products.class) {
                if (!p.isFlag()) {
                    try {
                        Products.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Log.e("_Customer", Thread.currentThread().getName() + "----消費者------" + consume());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException i) {
                }
                p.setFlag(false);
                Products.class.notify();
            }
        }
    }
}

//測試
main(){
Products product = new Products();
        ProductFactory pro = new ProductFactory(product);
        _Customer con = new _Customer(product);
        Thread p = new Thread(pro);
        Thread c = new Thread(con);
        p.start();
        c.start();
}

 來看上邊代碼,顯然重點就在線程的run方法中,首先啓動兩個線程,默認產品的isFlag屬性爲false,目的是控制生產者和消費者兩線程,讓消費者線程wait睡眠,而生產者線程生產產品,當生成一個產品,isFlag屬性改爲true,同時喚醒Products.class這個類對應線程池中沉睡的線程,isFlag屬性繼續控制線程,這時是生產者線程wait,消費者線程開啓,去消費一個產品,然後一直循環下去。
下班用lock鎖來實現單生產者單消費者模式:

//產品類
public class Products_1 {
    private String name;
    private int count = 1;
    private boolean flag = false;//爲wait和notify提供判斷標記
    //爲生產者和消費者提供同一個鎖對象和同一個condition
    public Lock lock = new ReentrantLock();
    public Condition condition = lock.newCondition();
    }
//生產者
package com.my_project.test_more_thread.product;

import android.util.Log;

import com.my_project.test_more_thread.goods.Products_1;

/**
 * Created by Administrator on 2019\2\25 0025.
 */

public class ProductFactory_1 implements Runnable {
    private Products_1 p;

    public ProductFactory_1(Products_1 product_1) {
        this.p = product_1;
    }

    //對外暴露生產產品的方法
    public void startProduct(String name) {
        p.setName(name + p.getCount());
        p.setCount(p.getCount() + 1);
    }

    @Override
    public void run() {
        while (true) {
            //使用lock鎖
            p.lock.lock();
            try {
                if (p.isFlag()) {
                    //沒有在消費,說明倉庫沒有產品了,生產者不需要睡眠,趕緊醒來工作了
                    try {
                        p.condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                startProduct("漢堡");
                Log.e("ProductFactory", Thread.currentThread().getName() + "----生產者------" + p.getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                p.setFlag(true);
                p.condition.signal();
            } catch (Exception e) {
            } finally {
                p.lock.unlock();
            }
        }
    }
}
//消費者
package com.my_project.test_more_thread.customer;

import android.util.Log;

import com.my_project.test_more_thread.goods.Products_1;

/**
 * Created by Administrator on 2019\2\25 0025.
 */

public class _Customer_1 implements Runnable {

    private Products_1 p;

    public _Customer_1(Products_1 product_1) {
        this.p = product_1;
    }

    //提供消費產品的方法
    public String consume() {
        return p.getName();
    }

    @Override
    public void run() {
        while (true) {
            p.lock.lock();
            try {
                if (!p.isFlag()) {
                    try {
                        p.condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Log.e("_Customer", Thread.currentThread().getName() + "----消費者------" + consume());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException i) {
                }
                p.setFlag(false);
                p.condition.signal();
            } catch (Exception e) {
            } finally {
                p.lock.unlock();
            }
        }
    }
}

 從代碼來看其實與synchronized同步沒什麼區別,只是finally中必須去解鎖,下面再來看看多生產者多消費者模式,但是產品在絕對某個時刻數量還是隻有一個的模式,其實這樣的模式現實中是沒有的,沒有哪個流水線會只做一個產品出來,這裏只是爲了循序漸進,更能清晰的說明是如何從單生產者單消費者模式演變而來的 在這裏插入圖片描述
從單生產者單消費者到多生產者多消費者模式,因爲線程安全問題和死鎖問題,有兩個問題需要考慮:

  • 對某一方來說,如何讓多線程達到和單線程一樣的生產或消費功能?多線程和單線程最大的區別就是線程間的安全問題,只要保證線程間任務執行的同步
  • 兩方如何協調配合完成生產消費功能,也就是說,當生產時,消費線程睡眠,消費時,生產線程睡眠,只需在某一方執行完同步任務時,喚醒另一方即可。

實質上,多線程就兩個問題,同步不同步和死鎖。

  1. 當生產方和消費方都出現了多線程,可以將生產方的多線程看成一個線程整體、消費方的多線程也看成一個整體,這解決的是線程安全問題。
  2. )再將生產方整體和消費方整體兩方結合起來看成多線程,來解決死鎖問題,而java中解決死鎖的方式就是喚醒對方或喚醒所有。

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

synchronized (Products.class) {
                if (!p.isFlag()) {
                    try {
                        Products.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Log.e("_Customer", Thread.currentThread().getName() + "----消費者------" + consume());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException i) {
                }
                p.setFlag(false);
                Products.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。

 @Override
    public void run() {
        while (true) {
            synchronized (Products.class) {
                while (!p.isFlag()) {
                    try {  Products.class.wait(); } catch (InterruptedException e) {e.printStackTrace();}
                 }
                Log.e("_Customer", Thread.currentThread().getName() + "----消費者------" + consume());
                try {Thread.sleep(1000);} catch (InterruptedException i) {}
                p.setFlag(false);
                Products.class.notify();
            }
        }
    }

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

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

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

//產品
public class Products {
    private String name;
    private int count = 1;
    private boolean flag = false;//爲wait和notify提供判斷標記
}
//生產者
package com.my_project.test_more_thread.product;

import android.util.Log;

import com.my_project.test_more_thread.goods.Products;

/**
 * Created by Administrator on 2019\2\25 0025.
 */

public class ProductFactory implements Runnable {
    private Products p;

    public ProductFactory(Products product) {
        this.p = product;
    }

    //對外暴露生產產品的方法
    public void startProduct(String name) {
        p.setName(name + p.getCount());
        p.setCount(p.getCount() + 1);
    }

    @Override
    public void run() {
        while (true) {
            synchronized (Products.class) {
                while (p.isFlag()){
                    //沒有在消費,說明倉庫沒有產品了,生產者不需要睡眠,趕緊醒來工作了
                    try {Products.class.wait();} catch (InterruptedException e) {e.printStackTrace();}
                }
                startProduct("漢堡");
                Log.e("ProductFactory", Thread.currentThread().getName() + "----生產者------" + p.getName());
                try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
                p.setFlag(true);
                Products.class.notifyAll();
            }
        }
    }
}
//消費者
package com.my_project.test_more_thread.customer;

import android.util.Log;

import com.my_project.test_more_thread.goods.Products;

/**
 * Created by Administrator on 2019\2\25 0025.
 */

public class _Customer implements Runnable {

    private Products p;

    public _Customer(Products product) {
        this.p = product;
    }

    //提供消費產品的方法
    public String consume() {
        return p.getName();
    }

    @Override
    public void run() {
        while (true) {
            synchronized (Products.class) {
                while (!p.isFlag()) {
                    try {Products.class.wait(); } catch (InterruptedException e) {e.printStackTrace();}
                }
                Log.e("_Customer", Thread.currentThread().getName() + "----消費者------" + consume());
                try {Thread.sleep(1000);} catch (InterruptedException i) {}
                p.setFlag(false);
                Products.class.notifyAll();
            }
        }
    }
}

//測試
main(){
Products product = new Products();
        ProductFactory pro = new ProductFactory(product);
        _Customer con = new _Customer(product);
        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()喚醒對方線程的方法。

//產品
public class Products_1 {
    private String name;
    private int count = 1;
    private boolean flag = false;//爲wait和notify提供判斷標記
    //爲生產者和消費者提供同一個鎖對象和同一個condition
    public  Lock lock = new ReentrantLock();
    public  Condition pro_con = lock.newCondition();
    public  Condition con_con = lock.newCondition();
    }
//生產者
package com.my_project.test_more_thread.product;

import android.util.Log;

import com.my_project.test_more_thread.goods.Products_1;

/**
 * Created by Administrator on 2019\2\25 0025.
 */

public class ProductFactory_1 implements Runnable {
    private Products_1 p;

    public ProductFactory_1(Products_1 product_1) {
        this.p = product_1;
    }

    //對外暴露生產產品的方法
    public void startProduct(String name) {
        p.setName(name + p.getCount());
        p.setCount(p.getCount() + 1);
    }

    @Override
    public void run() {
        while (true) {
            //使用lock鎖
            p.lock.lock();
            try {
                while (p.isFlag()){
                    //沒有在消費,說明倉庫沒有產品了,生產者不需要睡眠,趕緊醒來工作了
                    try {p.pro_con.await();} catch (InterruptedException e) {e.printStackTrace();}
                }
                startProduct("漢堡");
                Log.e("ProductFactory", Thread.currentThread().getName() + "----生產者------" + p.getName());
                try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
                p.setFlag(true);
                p.con_con.signal();
            } catch (Exception e) {
            } finally {
                p.lock.unlock();
            }
        }
    }
}

//消費者    
package com.my_project.test_more_thread.customer;

import android.util.Log;

import com.my_project.test_more_thread.goods.Products_1;

/**
 * Created by Administrator on 2019\2\25 0025.
 */

public class _Customer_1 implements Runnable {

    private Products_1 p;

    public _Customer_1(Products_1 product_1) {
        this.p = product_1;
    }

    //提供消費產品的方法
    public String consume() {
        return p.getName();
    }

    @Override
    public void run() {
        while (true) {
            p.lock.lock();
            try {
                while (!p.isFlag()){
                    try {p.con_con.await();} catch (InterruptedException e) {e.printStackTrace();}
                }
                Log.e("_Customer", Thread.currentThread().getName() + "----消費者------" + consume());
                try {Thread.sleep(1000);} catch (InterruptedException i) {}
                p.setFlag(false);
                p.pro_con.signal();
            } catch (Exception e) {
            } finally {
                p.lock.unlock();
            }
        }
    }
}
//測試
main(){
 Products_1 product = new Products_1();
        ProductFactory_1 pro = new ProductFactory_1(product);
        _Customer_1 con = new _Customer_1(product);
        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會均勻切換兩個線程,而synchronized同步代碼塊則有點聽天由命的感覺,lock加鎖後的兩個生產線程,
在這裏插入圖片描述關於多生產、多消費問題做個總結:

(1).解決某一方多線程不同步的方案是使用while(flag)來判斷是否wait;

(2).解決雙方死鎖問題的方案是喚醒對方,可以使用notifyAll(),signalAll()或對方監視器的signal()方法。

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

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

//產品
public class Products_2 {
    private String name;
    public Products_2(String name, int i) {
        this.name = name + i;
    }
    public String getName() {
        return name;
    }
}
//生產者
public class ProductFactory_2 implements Runnable {
    public static int num = 1;
    private _Basket mList;

    public ProductFactory_2(_Basket _basket) {
        this.mList = _basket;
    }

    @Override
    public void run() {
        while (true) {
            mList.in();
            try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
        }
    }
}
//消費者
public class _Customer_2 implements Runnable {

    private _Basket mList;
    public _Customer_2(_Basket b) {
        this.mList = b;
    }

    @Override
    public void run() {
        while (true) {
            Products_2 out = mList.out();
            try {Thread.sleep(1000);} catch (InterruptedException i) {}
        }
    }
}

//放產品的容器籃子
class Basket {
 private Products_2 [] arr;
 //the size of basket
 Basket(int size){
  arr = new Products_2 [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 Products_2 ("MianBao",Products_2 .num++);
   System.out.println("Put the Products_2 : "+arr[in_ptr].getName()+"------into Products_2 ["+in_ptr+"]");
   left++;
   if(++in_ptr == arr.length){in_ptr = 0;}
   empty.signal();
  } finally {
   lock.unlock();
  }
 }
 //bread out from basket
 public Products_2 out(){
  lock.lock();
  try{
   while(left == 0){
    try{empty.await();} catch (InterruptedException i) {i.printStackTrace();}
   }
   Products_2  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();
  }
 }
}
//上邊是容器籃子中只要有產品,消費者立馬去消費 ,下邊再來一種  容器滿了生產者wait  消費者喚醒,爲0後
//消費者wait,生產者喚醒

public class _Basket {
    private Products_2[] arr;

    //the size of basket
    public _Basket(int size) {
        arr = new Products_2[size];
    }

    //the pointer of in and out
    private int in_ptr, out_ptr;
    //how many product left in basket
    private int left;
    private volatile boolean flag = false;
    private Lock lock = new ReentrantLock();
    private Condition pro_lock = lock.newCondition();
    private Condition cus_lock = lock.newCondition();

    //product into basket
    public void in() {
        lock.lock();
        try {
            while (flag) {
                try {
                    cus_lock.signal();
                    Log.e("in----------", "我要開始睡了" + left + out_ptr);
                    pro_lock.await();
                    Log.e("in----------", "我被叫醒來了" + left + out_ptr);
                } catch (InterruptedException i) {
                    i.printStackTrace();
                }
            }
            arr[in_ptr] = new Products_2("漢堡", ProductFactory_2.num++);

            Log.e("Put the product: ", arr[in_ptr].getName() + "------into basket[" + in_ptr + "]");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            left++;
            Log.e("in----------", "left------" + left);
            if (++in_ptr == arr.length) {
                in_ptr = 0;
                flag = true;
            }

        } finally {
            lock.unlock();
        }
    }

    //bread out from basket
    public Products_2 out() {
        lock.lock();
        try {
            while (!flag) {
                try {
                    pro_lock.signal();
                    Log.e("out----------", "我要開始睡了" + left + out_ptr);
                    cus_lock.await();

                    Log.e("out----------", "我被叫醒來了" + left + out_ptr);
                } catch (InterruptedException i) {
                    i.printStackTrace();
                }
            }
            Products_2 out_Products_2 = arr[out_ptr];
            Log.e("Get the bread:", out_Products_2.getName() + "-----------from basket[" + out_ptr + "]");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            left--;
            Log.e("out----------", "left------" + left);
            if (++out_ptr == arr.length) {
                out_ptr = 0;
            }
            if (left == 0) {
                flag = false;
            }
            return out_Products_2;
        } finally {
            lock.unlock();
        }
    }
}

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章