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


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

// 以synchronized代碼塊為例

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

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

} // 此時釋放鎖

之所以說是隱式白修,是因為如果對此內(nèi)容不夠了解的話,僅從以上代碼根本看不出鎖的體現(xiàn)戒悠,但從JDK1.5版本開始熬荆,就可以對鎖進行顯式的操作,增加了一個Lock接口绸狐,實際上是將鎖進行了面向?qū)ο舐笨遥琇ock接口實現(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 + "個" + name;
        System.out.println(Thread.currentThread().getName() + "生產(chǎn)了" + this.name);
        count++;
        flag = true;
        notifyAll();
        }
        /*
         * 為了保證線程正常運行
         * unlock()方法一定要執(zhí)行
         * 因此將該方法放入finally塊中
         * 但是finally塊不能單獨使用
         * 因此使用try塊予以配合
         */
        finally{
        lock.unlock();// 釋放鎖
        }
    }

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

} 

此時結(jié)果如下:

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


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

JDK1.5版本對喚醒等待方法也進行了面向?qū)ο笈鹨唬碈ondition接口,該接口將Object類中的監(jiān)視器方法(wait()梦抢、notify()和 notifyAll())分解成截然不同的對象般贼,以便通過將這些對象與任意Lock實現(xiàn)組合使用。其中奥吩,Lock替代了synchronized方法和語句的使用具伍,Condition替代了 Object 監(jiān)視器方法的使用。Condition的實例實質(zhì)上被綁定到一個鎖上圈驼。要為特定Lock實例獲得Condition實例,可使用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鎖綁定
                 * 因此實際上依然是讓持有特定鎖的線程進入等待
                 */
                con.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.name = count + "個" + name;
        System.out.println(Thread.currentThread().getName() + "生產(chǎn)了" + this.name);
        count++;
        flag = true;
        // 同理用condition對象喚醒所有持有l(wèi)ock鎖的線程
        con.signalAll();;
        }
        finally{
        lock.unlock();
        }
    }

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

} 

此時結(jié)果如下:

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


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

在JDK1.5版本之前玉锌,只能使用synchronized方法實現(xiàn)同步名挥,但synchronized方法的局限性是,wait()方法主守、notify()方法都必須放在synchronized代碼塊內(nèi)部禀倔,且操作鎖上線程的方法與鎖都是綁定在一起的,但是JDK1.5版本之后可以使用Lock接口直接創(chuàng)建一把鎖参淫,而這把鎖可以使用newCondition()方法創(chuàng)建多個Condition對象救湖,每一個對象都可以單獨實現(xiàn)await()、signa()等方法涎才,但實際上這些Condition對象仍然使用的是同一把鎖鞋既,因此就實現(xiàn)了單獨控制線程而不需要統(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對象,控制消費線程的等待與喚醒
    private Condition con2 = lock.newCondition();

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

    // 消費產(chǎn)品的功能
    public void consume() {
        
        lock.lock();
        try{
        while (!flag) {
            try {
                // 消費線程con2進入等待
                con2.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "消費了" + 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("筆記本");
        }

    }

}

//消費任務(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)線程與消費線程棕兼,是因為線程T0陡舅、T1執(zhí)行的就是producer方法,在執(zhí)行該方法的con1.await();代碼時程储,T0或T1線程就會等待蹭沛,因此也就保證con1與生產(chǎn)線程實現(xiàn)了綁定,此時con2喚醒的只能是T2或T3線程章鲤,同理摊灭,消費線程也是如此,實現(xiàn)了程序性能的提升败徊。


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

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)品價格
    private double price;
    // 存放產(chǎn)品的容器
    private Clothes[] arr = new Clothes[100];
    // 創(chuàng)建生產(chǎn)使用的下標
    private int propointer;
    // 創(chuàng)建消費使用的下標
    private int conpointer;
    // 創(chuàng)建一把鎖
    private Lock lock = new ReentrantLock();
    // 得到與鎖綁定的condition對象,控制生產(chǎn)線程的等待與喚醒
    private Condition pro = lock.newCondition();
    // 得到與鎖綁定的condition對象皱蹦,控制消費線程的等待與喚醒
    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)容器滿時不生產(chǎn) 
             * 即產(chǎn)品數(shù)量與容器容量相同
             */
            while (count == arr.length) {
                try {
                    // 生產(chǎn)線程pro進入等待
                    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)下標 
             * 如果下標加一之后與容量相同 
             * 說明容器已滿 下標清零
             */
            if (++propointer == arr.length) {
                propointer = 0;
            }
            // 喚醒消費線程con
            con.signal();
            ;
        } finally {
            lock.unlock();
        }
    }

    /*
     * 因為輸出arr[propointer] 
     * 實際上Clothes類型的對象 
     * 輸出對象默認調(diào)用其toString方法 
     * 因此還需要重寫該方法
     */
    public String toString() {
        return "價格為" + price + "英鎊的" + name;

    }

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

        lock.lock();
        try {
            /*
             * 先判斷是否可以消費 
             * 當(dāng)產(chǎn)品數(shù)量為0時 
             * 不能消費
             */
            while (count == 0) {
                try {
                    // 消費線程con進入等待
                    con.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 消費一件產(chǎn)品
            Clothes clothes = arr[conpointer];
            System.out.println(Thread.currentThread().getName() + "消費了" + clothes);
            // 產(chǎn)品總量減一
            count--;
            /*
             * 判斷消費下標 
             * 如果下標加一之后與容量相同 
             * 說明已取到最后一件產(chǎn)品 
             * 下標清零
             */
            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();
        }

    }

}

// 消費任務(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();

    }

}

此時結(jié)果如下:


版權(quán)聲明:歡迎轉(zhuǎn)載,歡迎擴散沪哺,但轉(zhuǎn)載時請標明作者以及原文出處沈自,謝謝合作!             ↓↓↓
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辜妓,一起剝皮案震驚了整個濱河市枯途,隨后出現(xiàn)的幾起案子忌怎,更是在濱河造成了極大的恐慌,老刑警劉巖酪夷,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件榴啸,死亡現(xiàn)場離奇詭異,居然都是意外死亡晚岭,警方通過查閱死者的電腦和手機鸥印,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坦报,“玉大人库说,你說我怎么就攤上這事×鞘” “怎么了璃弄?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長构回。 經(jīng)常有香客問我夏块,道長,這世上最難降的妖魔是什么纤掸? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任脐供,我火速辦了婚禮,結(jié)果婚禮上借跪,老公的妹妹穿的比我還像新娘政己。我一直安慰自己,他們只是感情好掏愁,可當(dāng)我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布歇由。 她就那樣靜靜地躺著,像睡著了一般果港。 火紅的嫁衣襯著肌膚如雪沦泌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天辛掠,我揣著相機與錄音谢谦,去河邊找鬼。 笑死萝衩,一個胖子當(dāng)著我的面吹牛回挽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播猩谊,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼千劈,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了牌捷?” 一聲冷哼從身側(cè)響起墙牌,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤袁梗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后憔古,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡淋袖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年鸿市,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片即碗。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡焰情,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出剥懒,到底是詐尸還是另有隱情内舟,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響悼瓮,放射性物質(zhì)發(fā)生泄漏系草。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一韩肝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦垒在、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至旅挤,卻和暖如春踢关,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谦铃。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工耘成, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人驹闰。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓瘪菌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親嘹朗。 傳聞我的和親對象是個殘疾皇子师妙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,678評論 2 354

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

  • 一:java概述:1,JDK:Java Development Kit屹培,java的開發(fā)和運行環(huán)境默穴,java的開發(fā)工...
    ZaneInTheSun閱讀 2,650評論 0 11
  • 一怔檩、進程和線程 進程 進程就是一個執(zhí)行中的程序?qū)嵗總€進程都有自己獨立的一塊內(nèi)存空間蓄诽,一個進程中可以有多個線程薛训。...
    阿敏其人閱讀 2,612評論 0 13
  • Java8張圖 11、字符串不變性 12仑氛、equals()方法乙埃、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,701評論 0 11
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法锯岖,類相關(guān)的語法介袜,內(nèi)部類的語法,繼承相關(guān)的語法出吹,異常的語法遇伞,線程的語...
    子非魚_t_閱讀 31,625評論 18 399
  • 圖文/無為跑者 根吮甘露枝葉歡, 招蜂引蝶花兒鮮捶牢。 人間幸福何處覓鸠珠, 廳堂聚笑廚溢煙。
    最家游閱讀 415評論 18 36