JAVA并發(fā)(12)— Lock實(shí)現(xiàn)生產(chǎn)者消費(fèi)者

無論是synchronize還是Lock木人,均可以將其看做為Monitor模型。

1. 條件變量&生產(chǎn)者消費(fèi)者模型

在Monitor管程中,存在條件變量黔夭。

條件變量是管程內(nèi)的一種數(shù)據(jù)結(jié)構(gòu),且只有在管程中才能訪問它捂贿,它對管程內(nèi)的所有過程是全局的纠修。只能通過兩個(gè)原子性操作來操縱它:

  1. c.wait():將調(diào)用線(進(jìn))程移入條件變量c所持有的隊(duì)列中,并釋放管程厂僧,直到另一個(gè)線(進(jìn))程在條件變量c上調(diào)用signal()方法釋放線程扣草。
  2. c.signal():會(huì)釋放條件變量c隊(duì)列上持有的阻塞線(進(jìn))程。

而在synchronized關(guān)鍵字中颜屠,在MonitorObject內(nèi)置了一個(gè)條件變量辰妙。于是我們在同步塊中會(huì)使用wait()notify()方法后,會(huì)將同步塊中的線程均放入到MonitorObjectWait Set中甫窟,并進(jìn)行阻塞密浑。Wait Set中存在的是由于不同條件被阻塞的線程。會(huì)導(dǎo)致我們喚醒方法時(shí)粗井,只能使用notifyAll()喚醒所有的線程尔破。

public class SyncDemo {
    
    private List<String> list = new ArrayList<>(10);

    public void producer() {
        synchronized (this) {
            while (list.size() == 10) {
                try {
                    //若隊(duì)列滿了,將線程存入WaitSet中浇衬,等待被喚醒
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            list.add("產(chǎn)品");
            this.notifyAll();
        }
    }

    public void consumer() {
        synchronized (this) {
            while (list.size() == 0) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        list.remove(0);
        //喚醒生產(chǎn)者
        this.notifyAll();
    }
}

Lock中懒构,用戶可以定義多個(gè)條件變量,即Condition耘擂,若未滿足條件胆剧,則會(huì)存儲到不同的條件變量所維護(hù)的隊(duì)列中,以便可以喚醒不同條件隊(duì)列的線程醉冤。

條件變量則允許線程由于一些未達(dá)到的條件而阻塞秩霍,此處的“條件”可以由用戶來定義篙悯,在訪問該條件時(shí)需要加鎖(互斥量),如果條件沒達(dá)到铃绒,線程將阻塞在該條件上鸽照。

public class Demo {
    //共享變量
    private List<String> list = new ArrayList<>(10);
    //互斥量
    private ReentrantLock lock = new ReentrantLock();
    //條件變量[不為空]。若不符合條件匿垄,則加入到條件變量到Condition queue中
    private Condition notEmptyCondition = lock.newCondition();
    //條件變量[不為滿]移宅。若不符合條件,則加入到條件變量到Condition queue中
    private Condition notFullCondition = lock.newCondition();
    /**
     * 生產(chǎn)者模型
     */
    public void producer() {
        try {
            lock.lock();
            //若list滿了
            while (list.size() == 10) {
                try {
                    //線程進(jìn)入椿疗,則將線程放入到不為full的條件變量中
                    //禁止再次生產(chǎn)漏峰。
                    notFullCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            list.add("元素");
            //喚醒生產(chǎn)者
            notEmptyCondition.signal();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 消費(fèi)者模型
     */
    public void consumer() {
        try {
            lock.lock();
            //若list為空
            while (list.size() == 0) {
                try {
                    //將該線程放入到條件變量中,該條件變量為不為空
                    //禁止再次消費(fèi)
                    notEmptyCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            list.remove(0);
            notFullCondition.signal();
        } finally {
            lock.unlock();
        }
    }
}

2. ArrayBlockingQueue阻塞隊(duì)列

阻塞隊(duì)列ArrayBlockingQueue實(shí)際上也是使用生產(chǎn)者-消費(fèi)者模型來實(shí)現(xiàn)的届榄。

public class BlockingQueueDemo {
    private static BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(1);
    public static void main(String[] args) throws InterruptedException {
        /**
         * 增加元素
         */
        //如果隊(duì)列滿了浅乔,則拋出異常。
        blockingQueue.add("元素");
        //如果隊(duì)列滿了铝条,則返回false靖苇。
        blockingQueue.offer("元素");
        //如果隊(duì)列滿了,則線程阻塞班缰。
        blockingQueue.put("元素2");
        //如果隊(duì)列滿了贤壁,等待一段時(shí)間后依舊滿,則返回false埠忘。
        blockingQueue.offer("元素2",1,TimeUnit.MICROSECONDS);
        /**
         * 移除元素
         */
        //如果隊(duì)列為空脾拆,則線程阻塞。
        blockingQueue.take();
        //如果隊(duì)列為空莹妒,則拋出異常名船。
        blockingQueue.remove();
        //如果隊(duì)列為空,阻塞一段時(shí)間后依舊為null旨怠,則返回null渠驼。
        blockingQueue.poll(1, TimeUnit.MILLISECONDS);
        //如果隊(duì)列為空,則返回null鉴腻。
        blockingQueue.poll();
    }
}

生產(chǎn)者:當(dāng)count為items.length數(shù)量時(shí)迷扇,即隊(duì)列已滿,不滿足notFull的條件變量爽哎,所以線程會(huì)進(jìn)入notFull所維護(hù)的條件隊(duì)列中阻塞谋梭。

public void put(E e) throws InterruptedException {  
    checkNotNull(e);  
    final ReentrantLock lock = this.lock;  
    lock.lockInterruptibly();  
    try {  
        while (count == items.length)  
            notFull.await();  
        enqueue(e);  
    } finally {  
        lock.unlock();  
    }  
}  

消費(fèi)者:當(dāng)items.length數(shù)量為0時(shí)。那么不滿足notEmpty條件變量倦青。該線程會(huì)加入到notEmpty所維護(hù)的條件隊(duì)列中。

public E take() throws InterruptedException {  
    final ReentrantLock lock = this.lock;  
    lock.lockInterruptibly();  
    try {  
        while (count == 0)  
            notEmpty.await();  
        return dequeue();  
    } finally {  
        lock.unlock();  
    }  
}  

推薦閱讀

條件變量與互斥量

相關(guān)閱讀

JAVA并發(fā)(1)—java對象布局
JAVA并發(fā)(2)—PV機(jī)制與monitor(管程)機(jī)制
JAVA并發(fā)(3)—線程運(yùn)行時(shí)發(fā)生GC盹舞,會(huì)回收ThreadLocal弱引用的key嗎产镐?
JAVA并發(fā)(4)— ThreadLocal源碼角度分析是否真正能造成內(nèi)存溢出隘庄!
JAVA并發(fā)(5)— 多線程順序的打印出A,B癣亚,C(線程間的協(xié)作)
JAVA并發(fā)(6)— AQS源碼解析(獨(dú)占鎖-加鎖過程)
JAVA并發(fā)(7)—AQS源碼解析(獨(dú)占鎖-解鎖過程)
JAVA并發(fā)(8)—AQS公平鎖為什么會(huì)比非公平鎖效率低(源碼分析)
JAVA并發(fā)(9)— 共享鎖的獲取與釋放
JAVA并發(fā)(10)—interrupt喚醒掛起線程
JAVA并發(fā)(11)—AQS源碼Condition阻塞和喚醒
JAVA并發(fā)(12)— Lock實(shí)現(xiàn)生產(chǎn)者消費(fèi)者

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末丑掺,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子述雾,更是在濱河造成了極大的恐慌街州,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件玻孟,死亡現(xiàn)場離奇詭異唆缴,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)黍翎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門面徽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人匣掸,你說我怎么就攤上這事趟紊。” “怎么了碰酝?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵担孔,是天一觀的道長。 經(jīng)常有香客問我衅檀,道長咙好,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任碱璃,我火速辦了婚禮弄痹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嵌器。我一直安慰自己肛真,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布爽航。 她就那樣靜靜地躺著蚓让,像睡著了一般。 火紅的嫁衣襯著肌膚如雪讥珍。 梳的紋絲不亂的頭發(fā)上历极,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機(jī)與錄音衷佃,去河邊找鬼趟卸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的锄列。 我是一名探鬼主播图云,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼邻邮!你這毒婦竟也來了竣况?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤筒严,失蹤者是張志新(化名)和其女友劉穎丹泉,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸭蛙,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡摹恨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了规惰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片睬塌。...
    茶點(diǎn)故事閱讀 38,789評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖歇万,靈堂內(nèi)的尸體忽然破棺而出揩晴,到底是詐尸還是另有隱情,我是刑警寧澤贪磺,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布硫兰,位于F島的核電站,受9級特大地震影響寒锚,放射性物質(zhì)發(fā)生泄漏劫映。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一刹前、第九天 我趴在偏房一處隱蔽的房頂上張望泳赋。 院中可真熱鬧,春花似錦喇喉、人聲如沸祖今。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽千诬。三九已至,卻和暖如春膏斤,著一層夾襖步出監(jiān)牢的瞬間徐绑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工莫辨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留傲茄,地道東北人毅访。 一個(gè)月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像烫幕,于是被迫代替她去往敵國和親俺抽。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評論 2 351