(五)使用Lock接口與Condition接口實(shí)現(xiàn)生產(chǎn)者與消費(fèi)者


之前使用synchronized實(shí)現(xiàn)生產(chǎn)者與消費(fèi)者,雖然可行,也沒有錯(cuò)誤,但是最終喚醒全部線程的做法會犧牲程序的性能,造成無謂的浪費(fèi),在JDK1.5版本之前,對鎖的操作時(shí)隱式的,只能使用synchronized實(shí)現(xiàn)同步鎖的效果:

// 以synchronized代碼塊為例

synchronized (對象) {// 此時(shí)獲取鎖

    // 要執(zhí)行的任務(wù)

} // 此時(shí)釋放鎖

之所以說是隱式,是因?yàn)槿绻麑Υ藘?nèi)容不夠了解的話,僅從以上代碼根本看不出鎖的體現(xiàn),但從JDK1.5版本開始,就可以對鎖進(jìn)行顯式的操作,增加了一個(gè)Lock接口,實(shí)際上是將鎖進(jìn)行了面向?qū)ο螅琇ock接口實(shí)現(xiàn)提供了比synchronized方法和語句可獲得的更廣泛的鎖定操作。

1、使用Lock修改示例代碼

此處僅使用Lock替代同步代碼塊,其余部分不變:
1.使用Lock接口子類ReentrantLock創(chuàng)建一把鎖
2.把之前寫在同步代碼塊中的內(nèi)容寫在lock()方法和unlock()方法之間

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//描述產(chǎn)品
class Product {
    private String name;
    private int count;
    private boolean flag;
    
    // 創(chuàng)建一把鎖
    private Lock lock = new ReentrantLock();

    // 生產(chǎn)產(chǎn)品的功能
    public void produce(String name) {
                    
        lock.lock();// 獲取鎖
        try{
        while (flag) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.name = count + "個(gè)" + name;
        System.out.println(Thread.currentThread().getName() + "生產(chǎn)了" + this.name);
        count++;
        flag = true;
        notifyAll();
        }
        /*
         * 為了保證線程正常運(yùn)行
         * unlock()方法一定要執(zhí)行
         * 因此將該方法放入finally塊中
         * 但是finally塊不能單獨(dú)使用
         * 因此使用try塊予以配合
         */
        finally{
        lock.unlock();// 釋放鎖
        }
    }

    // 消費(fèi)產(chǎn)品的功能
    public void consume() {
        
        // 使用同一把鎖   
        lock.lock();
        try{
        while (!flag) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "消費(fèi)了" + this.name);
        flag = false;
        notifyAll();
        }
        finally{
            lock.unlock();
        }
        
    }

} 

此時(shí)結(jié)果如下:

結(jié)果很明顯,出現(xiàn)了IllegalMonitorStateException(無效的監(jiān)視器狀態(tài)異常),發(fā)生該異常的原因也很簡單,歸根到底就是wait()方法與notifyAll()方法。之前講過,這些方法一定要用在同步當(dāng)中,并且由對象,也就是鎖來調(diào)用,當(dāng)對象是this時(shí)可以省略不寫,而現(xiàn)在用Lock接口代替了synchronized,因此也就意味著沒有同步方法,自然也就無法使用這些方法了,那么如何解決這個(gè)問題呢?


2、使用Condition接口實(shí)現(xiàn)等待喚醒

JDK1.5版本對喚醒等待方法也進(jìn)行了面向?qū)ο螅碈ondition接口,該接口將Object類中的監(jiān)視器方法(wait()、notify()和 notifyAll())分解成截然不同的對象,以便通過將這些對象與任意Lock實(shí)現(xiàn)組合使用。其中,Lock替代了synchronized方法和語句的使用,Condition替代了 Object 監(jiān)視器方法的使用。Condition的實(shí)例實(shí)質(zhì)上被綁定到一個(gè)鎖上。要為特定Lock實(shí)例獲得Condition實(shí)例,可使用Lock接口中的newCondition()方法,示例代碼如下:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//描述產(chǎn)品
class Product {
    private String name;
    private int count;
    private boolean flag;
    
    private Lock lock = new ReentrantLock();
    
    // 得到與鎖綁定的condition對象
    private Condition con = lock.newCondition();

    // 生產(chǎn)產(chǎn)品的功能
    public void produce(String name) {
        
        lock.lock();
        try{
        while (flag) {
            try {
                /*
                 * 雖然是用condition對象調(diào)用await()方法
                 * 但由于condition對象已經(jīng)于lock鎖綁定
                 * 因此實(shí)際上依然是讓持有特定鎖的線程進(jìn)入等待
                 */
                con.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.name = count + "個(gè)" + name;
        System.out.println(Thread.currentThread().getName() + "生產(chǎn)了" + this.name);
        count++;
        flag = true;
        // 同理用condition對象喚醒所有持有l(wèi)ock鎖的線程
        con.signalAll();;
        }
        finally{
        lock.unlock();
        }
    }

    // 消費(fèi)產(chǎn)品的功能
    public void consume() {
        // 使用同一把鎖
        
        lock.lock();
        try{
        while (!flag) {
            try {
                con.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "消費(fèi)了" + this.name);
        flag = false;
        con.signalAll();;
        }
        finally{
            lock.unlock();
        }
        
    }

} 

此時(shí)結(jié)果如下:

雖然目前使用Lock接口以及Condition接口實(shí)現(xiàn)了生產(chǎn)者與消費(fèi)者,但依然是喚醒全部線程,程序性能并沒有提升,此時(shí)就可以使用Lock接口與Condition接口的靈活性來避免這個(gè)問題。


3、JDK1.5版本后的多線程程序

在JDK1.5版本之前,只能使用synchronized方法實(shí)現(xiàn)同步,但synchronized方法的局限性是,wait()方法、notify()方法都必須放在synchronized代碼塊內(nèi)部,且操作鎖上線程的方法與鎖都是綁定在一起的,但是JDK1.5版本之后可以使用Lock接口直接創(chuàng)建一把鎖,而這把鎖可以使用newCondition()方法創(chuàng)建多個(gè)Condition對象,每一個(gè)對象都可以單獨(dú)實(shí)現(xiàn)await()、signa()等方法,但實(shí)際上這些Condition對象仍然使用的是同一把鎖,因此就實(shí)現(xiàn)了單獨(dú)控制線程而不需要統(tǒng)一喚醒,從而提升了程序的性能,示例代碼如下:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//描述產(chǎn)品
class Product {
    private String name;
    private int count;
    private boolean flag;
    
    private Lock lock = new ReentrantLock();
    
    // 得到與鎖綁定的condition對象,控制生產(chǎn)線程的等待與喚醒
    private Condition con1 = lock.newCondition();
    
    // 得到與鎖綁定的condition對象,控制消費(fèi)線程的等待與喚醒
    private Condition con2 = lock.newCondition();

    // 生產(chǎn)產(chǎn)品的功能
    public void produce(String name) {
        
        lock.lock();
        try{
        while (flag) {
            try {
            // 生產(chǎn)線程con1進(jìn)入等待
                con1.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.name = count + "個(gè)" + name;
        System.out.println(Thread.currentThread().getName() + "生產(chǎn)了" + this.name);
        count++;
        flag = true;
        // 喚醒消費(fèi)線程con2
        con2.signal();;
        }
        finally{
        lock.unlock();
        }
    }

    // 消費(fèi)產(chǎn)品的功能
    public void consume() {
        
        lock.lock();
        try{
        while (!flag) {
            try {
                // 消費(fèi)線程con2進(jìn)入等待
                con2.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "消費(fèi)了" + this.name);
        flag = false;
        // 喚醒生產(chǎn)線程con1
        con1.signal();;
        }
        finally{
            lock.unlock();
        }
        
    }

} 

//生產(chǎn)任務(wù)
class Producer implements Runnable {
    private Product pro;

    public Producer(Product pro) {
        this.pro = pro;

    }

    public void run() {
        while (true) {
            pro.produce("筆記本");
        }

    }

}

//消費(fèi)任務(wù)
class Consumer implements Runnable {
    private Product pro;

    public Consumer(Product pro) {
        this.pro = pro;

    }

    public void run() {
        while (true) {
            pro.consume();
        }
    }
}

public class Demo1 {

    public static void main(String[] args) {
        Product pro = new Product();
        
        Producer producer = new Producer(pro);
        Consumer consumer = new Consumer(pro);
        
        Thread t0 = new Thread(producer);
        Thread t1 = new Thread(producer);
        
        Thread t2 = new Thread(consumer);
        Thread t3 = new Thread(consumer);
                
        t0.start();
        t1.start();
        t2.start();
        t3.start();

    }

}

之所以con1和con2就可以控制生產(chǎn)線程與消費(fèi)線程,是因?yàn)榫€程T0、T1執(zhí)行的就是producer方法,在執(zhí)行該方法的con1.await();代碼時(shí),T0或T1線程就會等待,因此也就保證con1與生產(chǎn)線程實(shí)現(xiàn)了綁定,此時(shí)con2喚醒的只能是T2或T3線程,同理,消費(fèi)線程也是如此,實(shí)現(xiàn)了程序性能的提升。


4、使用Lock接口與Condition接口實(shí)現(xiàn)生產(chǎn)者與消費(fèi)者的完整示例代碼

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//描述產(chǎn)品
class Clothes {
    // 產(chǎn)品名稱
    private String name;
    // 產(chǎn)品價(jià)格
    private double price;
    // 存放產(chǎn)品的容器
    private Clothes[] arr = new Clothes[100];
    // 創(chuàng)建生產(chǎn)使用的下標(biāo)
    private int propointer;
    // 創(chuàng)建消費(fèi)使用的下標(biāo)
    private int conpointer;
    // 創(chuàng)建一把鎖
    private Lock lock = new ReentrantLock();
    // 得到與鎖綁定的condition對象,控制生產(chǎn)線程的等待與喚醒
    private Condition pro = lock.newCondition();
    // 得到與鎖綁定的condition對象,控制消費(fèi)線程的等待與喚醒
    private Condition con = lock.newCondition();
    // 記錄產(chǎn)品數(shù)量
    private int count;

    // 生成無參的構(gòu)造方法
    public Clothes() {

    }

    // 生成帶參的構(gòu)造方法
    public Clothes(String name, double price) {
        this.name = name;
        this.price = price;
    }

    // 生產(chǎn)產(chǎn)品的功能
    public void produce() {
        lock.lock();
        try {
            /*
             * 先判斷是否可以生成 
             * 當(dāng)容器滿時(shí)不生產(chǎn) 
             * 即產(chǎn)品數(shù)量與容器容量相同
             */
            while (count == arr.length) {
                try {
                    // 生產(chǎn)線程pro進(jìn)入等待
                    pro.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 將生產(chǎn)的衣服存入容器
            arr[propointer] = new Clothes("襯衣", 9.15);
            System.out.println(Thread.currentThread().getName() + "生產(chǎn)了" + arr[propointer] + ",此為第" + count + "件");
            // 生產(chǎn)后數(shù)量加一
            count++;
            /*
             * 判斷生產(chǎn)下標(biāo) 
             * 如果下標(biāo)加一之后與容量相同 
             * 說明容器已滿 下標(biāo)清零
             */
            if (++propointer == arr.length) {
                propointer = 0;
            }
            // 喚醒消費(fèi)線程con
            con.signal();
            ;
        } finally {
            lock.unlock();
        }
    }

    /*
     * 因?yàn)檩敵鯽rr[propointer] 
     * 實(shí)際上Clothes類型的對象 
     * 輸出對象默認(rèn)調(diào)用其toString方法 
     * 因此還需要重寫該方法
     */
    public String toString() {
        return "價(jià)格為" + price + "英鎊的" + name;

    }

    // 消費(fèi)產(chǎn)品的功能
    public void consume() {

        lock.lock();
        try {
            /*
             * 先判斷是否可以消費(fèi) 
             * 當(dāng)產(chǎn)品數(shù)量為0時(shí) 
             * 不能消費(fèi)
             */
            while (count == 0) {
                try {
                    // 消費(fèi)線程con進(jìn)入等待
                    con.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 消費(fèi)一件產(chǎn)品
            Clothes clothes = arr[conpointer];
            System.out.println(Thread.currentThread().getName() + "消費(fèi)了" + clothes);
            // 產(chǎn)品總量減一
            count--;
            /*
             * 判斷消費(fèi)下標(biāo) 
             * 如果下標(biāo)加一之后與容量相同 
             * 說明已取到最后一件產(chǎn)品 
             * 下標(biāo)清零
             */
            if (++conpointer == arr.length) {
                conpointer = 0;
            }
            // 喚醒生產(chǎn)線程pro
            pro.signal();
            ;
        } finally {
            lock.unlock();
        }

    }

}

// 生產(chǎn)任務(wù)
class Producer implements Runnable {
    private Clothes clo;

    public Producer(Clothes clo) {
        this.clo = clo;

    }

    public void run() {
        while (true) {
            clo.produce();
        }

    }

}

// 消費(fèi)任務(wù)
class Consumer implements Runnable {
    private Clothes clo;

    public Consumer(Clothes clo) {
        this.clo = clo;

    }

    public void run() {
        while (true) {
            clo.consume();
        }
    }
}

public class Demo2 {

    public static void main(String[] args) {
        Clothes clo = new Clothes();

        Producer producer = new Producer(clo);
        Consumer consumer = new Consumer(clo);

        Thread t0 = new Thread(producer);
        Thread t1 = new Thread(producer);

        Thread t2 = new Thread(consumer);
        Thread t3 = new Thread(consumer);

        t0.start();
        t1.start();
        t2.start();
        t3.start();

    }

}

此時(shí)結(jié)果如下:


版權(quán)聲明:歡迎轉(zhuǎn)載,歡迎擴(kuò)散,但轉(zhuǎn)載時(shí)請標(biāo)明作者以及原文出處,謝謝合作!             ↓↓↓
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,471評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內(nèi)容

  • 一:java概述:1,JDK:Java Development Kit,java的開發(fā)和運(yùn)行環(huán)境,java的開發(fā)工...
    ZaneInTheSun閱讀 2,685評論 0 11
  • 一、進(jìn)程和線程 進(jìn)程 進(jìn)程就是一個(gè)執(zhí)行中的程序?qū)嵗總€(gè)進(jìn)程都有自己獨(dú)立的一塊內(nèi)存空間,一個(gè)進(jìn)程中可以有多個(gè)線程。...
    阿敏其人閱讀 2,620評論 0 13
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,722評論 0 11
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,733評論 18 399
  • 圖文/無為跑者 根吮甘露枝葉歡, 招蜂引蝶花兒鮮。 人間幸福何處覓, 廳堂聚笑廚溢煙。
    最家游閱讀 431評論 18 36