java并發(fā)等待條件的實(shí)現(xiàn)原理詳解
前言
前面介紹了排它鎖,共享鎖的實(shí)現(xiàn)機(jī)制,本篇繼續(xù)學(xué)習(xí)AQS中的另外一個(gè)內(nèi)容-Condition。想必學(xué)過(guò)java的都知道Object.wait和Object.notify,同時(shí)也應(yīng)該知曉這兩個(gè)方法的使用離不開(kāi)synchronized關(guān)鍵字。synchronized是jvm級(jí)別提供的同步原語(yǔ),它的實(shí)現(xiàn)機(jī)制隱藏在jvm實(shí)現(xiàn)中。作為L(zhǎng)ock系列功能中的Condition,就是用來(lái)實(shí)現(xiàn)類(lèi)似 Object.wait和Object.notify 對(duì)應(yīng)功能的。
使用場(chǎng)景
為了更好的理解Lock和Condition的使用場(chǎng)景,下面我們先來(lái)實(shí)現(xiàn)這樣一個(gè)功能:有多個(gè)生產(chǎn)者,多個(gè)消費(fèi)者,一個(gè)產(chǎn)品容器,我們假設(shè)容器最多可以放3個(gè)產(chǎn)品,如果滿(mǎn)了,生產(chǎn)者需要等待產(chǎn)品被消費(fèi),如果沒(méi)有產(chǎn)品了,消費(fèi)者需要等待。我們的目標(biāo)是一共生產(chǎn)10個(gè)產(chǎn)品,最終消費(fèi)10個(gè)產(chǎn)品,如何在多線程環(huán)境下完成這一挑戰(zhàn)呢?下面是我簡(jiǎn)單實(shí)現(xiàn)的一個(gè)demo,僅供參考。
package com.lock.condition.test; import java.util.LinkedList; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class LockConditionTest { // 生產(chǎn) 和 消費(fèi) 的最大總數(shù) public static int totalCount = 10; // 已經(jīng)生產(chǎn)的產(chǎn)品數(shù) public static volatile int hasProduceCount = 0; // 已經(jīng)消費(fèi)的產(chǎn)品數(shù) public static volatile int hasConsumeCount = 0; // 容器最大容量 public static int containerSize = 3; // 使用公平策略的可重入鎖,便于觀察演示結(jié)果 public static ReentrantLock lock = new ReentrantLock(true); public static Condition notEmpty = lock.newCondition(); public static Condition notFull = lock.newCondition(); // 容器 public static LinkedList<integer> container = new LinkedList<integer>(); // 用于標(biāo)識(shí)產(chǎn)品 public static AtomicInteger idGenerator = new AtomicInteger(); public static void main(String[] args) { Thread p1 = new Thread(new Producer(), "p-1"); Thread p2 = new Thread(new Producer(), "p-2"); Thread p3 = new Thread(new Producer(), "p-3"); Thread c1 = new Thread(new Consumer(), "c-1"); Thread c2 = new Thread(new Consumer(), "c-2"); Thread c3 = new Thread(new Consumer(), "c-3"); c1.start(); c2.start(); c3.start(); p1.start(); p2.start(); p3.start(); try{ c1.join(); c2.join(); c3.join(); p1.join(); p2.join(); p3.join(); }catch(Exception e){ } System.out.println(" done. "); } static class Producer implements Runnable{ @Override public void run() { while(true){ lock.lock(); try{ // 容器滿(mǎn)了,需要等待非滿(mǎn)條件 while(container.size() >= containerSize){ notFull.await(); } // 到這里表明容器未滿(mǎn),但需要再次判斷是否已經(jīng)完成了任務(wù) if(hasProduceCount >= totalCount){ System.out.println(Thread.currentThread().getName()+" producer exit"); return ; } int product = idGenerator.incrementAndGet(); // 把生產(chǎn)出來(lái)的產(chǎn)品放入容器 container.addLast(product); System.out.println(Thread.currentThread().getName() + " product " + product); hasProduceCount++; // 通知消費(fèi)線程可以去消費(fèi)了 notEmpty.signal(); } catch (InterruptedException e) { }finally{ lock.unlock(); } } } } static class Consumer implements Runnable{ @Override public void run() { while(true){ lock.lock(); try{ if(hasConsumeCount >= totalCount){ System.out.println(Thread.currentThread().getName()+" consumer exit"); return ; } // 一直等待有產(chǎn)品了,再繼續(xù)往下消費(fèi) while(container.isEmpty()){ notEmpty.await(2, TimeUnit.SECONDS); if(hasConsumeCount >= totalCount){ System.out.println(Thread.currentThread().getName()+" consumer exit"); return ; } } Integer product = container.removeFirst(); System.out.println(Thread.currentThread().getName() + " consume " + product); hasConsumeCount++; // 通知生產(chǎn)線程可以繼續(xù)生產(chǎn)產(chǎn)品了 notFull.signal(); } catch (InterruptedException e) { }finally{ lock.unlock(); } } } } }
一次執(zhí)行結(jié)果如下:
p-1 product 1 p-3 product 2 p-2 product 3 c-3 consume 1 c-2 consume 2 c-1 consume 3 p-1 product 4 p-3 product 5 p-2 product 6 c-3 consume 4 c-2 consume 5 c-1 consume 6 p-1 product 7 p-3 product 8 p-2 product 9 c-3 consume 7 c-2 consume 8 c-1 consume 9 p-1 product 10 p-3 producer exit p-2 producer exit c-3 consume 10 c-2 consumer exit c-1 consumer exit p-1 producer exit c-3 consumer exit done.
從結(jié)果可以發(fā)現(xiàn)已經(jīng)達(dá)到我們的目的了。
深入理解Condition的實(shí)現(xiàn)原理
上面的示例只是為了展示 Lock結(jié)合Condition可以實(shí)現(xiàn)的一種經(jīng)典場(chǎng)景,在有了感性的認(rèn)識(shí)之后,我們將一步一步來(lái)觀察Lock和Condition是如何協(xié)作完成這一任務(wù)的,這也是本篇的核心內(nèi)容。
為了更好的理解和演示這一個(gè)過(guò)程,我們使用到的鎖是使用公平策略模式的,我們會(huì)使用上面例子運(yùn)作的流程。我們會(huì)使用到3個(gè)生產(chǎn)線程,3個(gè)消費(fèi)線程,分別表示 p1、p2、p3和c1、c2、c3。
Condition的內(nèi)部實(shí)現(xiàn)是使用節(jié)點(diǎn)鏈來(lái)實(shí)現(xiàn)的,每個(gè)條件實(shí)例對(duì)應(yīng)一個(gè)節(jié)點(diǎn)鏈,我們有notEmpty 和 notFull 兩個(gè)條件實(shí)例,所以會(huì)有兩個(gè)等待節(jié)點(diǎn)鏈。
一切準(zhǔn)備就緒 ,開(kāi)始我們的探索之旅。
1、線程c3執(zhí)行,然后發(fā)現(xiàn)沒(méi)有產(chǎn)品可以消費(fèi),執(zhí)行 notEmpty.await,進(jìn)入等待隊(duì)列中等候。
2、線程c2和線程c1執(zhí)行,然后發(fā)現(xiàn)沒(méi)有產(chǎn)品可以消費(fèi),執(zhí)行 notEmpty.await,進(jìn)入等待隊(duì)列中等候。
3、 線程 p1 啟動(dòng),得到了鎖,p1開(kāi)始生產(chǎn)產(chǎn)品,這時(shí)候p3搶在p2之前,執(zhí)行了lock操作,結(jié)果p2和p3都處于等待狀態(tài),入同步隊(duì)列等待。<喎�"/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxpbWcgYWx0PQ=="這里寫(xiě)圖片描述" src="/uploadfile/Collfiles/20160912/20160912092710536.png" title="\" />
注意,本例中我們使用的是公平策略模式下的排它鎖,由于p3搶先執(zhí)行取鎖操作,所以雖然p2和p3都被阻塞了,但是p3會(huì)優(yōu)先被喚醒 。
4、這會(huì),p1生產(chǎn)完畢,通知 not empty等待隊(duì)列,可以喚醒一個(gè)等待線程節(jié)點(diǎn)了,然后釋放了鎖,釋放鎖會(huì)導(dǎo)致p3被喚醒,然后p1進(jìn)入下一個(gè)循環(huán),進(jìn)入同步隊(duì)列。
事情開(kāi)始變得有趣了,p1執(zhí)行一次生產(chǎn)后,執(zhí)行了 notEmpty.signal,其效果就是把 not empty等待列表中的頭節(jié)點(diǎn),即c3節(jié)點(diǎn)移到同步等待列隊(duì)中,重新參與搶占鎖。
5、p3生產(chǎn)完了產(chǎn)品后,繼續(xù)notEmpty.signal,同時(shí)釋放鎖,釋放鎖后會(huì)喚醒p2線程,然后p3在下一輪嘗試獲取鎖的時(shí)候,再次入隊(duì)。
6、接著,p2繼續(xù)生產(chǎn),生產(chǎn)后執(zhí)行 notEmpty.signal,同時(shí)釋放鎖,釋放鎖后喚醒c3線程,然后p2在下一輪嘗試取鎖的時(shí)候,入列。
7、c3進(jìn)行消費(fèi),你可以看到,現(xiàn)在 not empty等待列隊(duì)中已經(jīng)沒(méi)有等待節(jié)點(diǎn)了,由于我們使用的是公平策略排它鎖,這就會(huì)導(dǎo)致同步隊(duì)列中的節(jié)點(diǎn)一個(gè)接著一個(gè)執(zhí)行,而目前同步隊(duì)列中的節(jié)點(diǎn)排列為一生產(chǎn),一消費(fèi),這不難可以知道,接下來(lái)代碼已經(jīng)不會(huì)進(jìn)入 wait條件了,所以一個(gè)一個(gè)輪流執(zhí)行就是,比如c3,執(zhí)行完了,繼續(xù)notFull.signal(); 然后釋放鎖,入隊(duì),這里要明白,notFull.signal();這句代碼其實(shí)沒(méi)有作用了,因?yàn)?not full等待隊(duì)列中沒(méi)有任何等待線程節(jié)點(diǎn)。 c3執(zhí)行后,狀態(tài)如下圖所示:
8、后面的事情我想大家都可以想得出來(lái)是怎樣一步一步交替執(zhí)行的了。
總結(jié)
本篇基于一個(gè)實(shí)例來(lái)演示結(jié)合Lock和Condition如何實(shí)現(xiàn)生產(chǎn)-消費(fèi)模式,而且只討論一種可能執(zhí)行的流程,是想更簡(jiǎn)單的表述AQS底層是如何實(shí)現(xiàn)的。基于上面這個(gè)演示過(guò)程,針對(duì)其它的執(zhí)行流程,其原來(lái)也是一樣的。Condition內(nèi)部使用一個(gè)節(jié)點(diǎn)鏈來(lái)保存所有 wait狀態(tài)的線程,當(dāng)對(duì)應(yīng)條件被signal的時(shí)候,就會(huì)把等待節(jié)點(diǎn)轉(zhuǎn)移到同步隊(duì)列中,繼續(xù)競(jìng)爭(zhēng)鎖。原理其實(shí)并不復(fù)雜,有興趣的朋友可以翻閱源碼。
以上就是本文關(guān)于java并發(fā)等待條件的實(shí)現(xiàn)原理詳解的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站:java并發(fā)學(xué)習(xí)之BlockingQueue實(shí)現(xiàn)生產(chǎn)者消費(fèi)者詳解、Java并發(fā)之嵌套管程鎖死詳解、Java系統(tǒng)的高并發(fā)解決方法詳解等,有什么問(wèn)題可以隨時(shí)留言,小編會(huì)及時(shí)回復(fù)大家的。感謝朋友們對(duì)本站的支持!
相關(guān)文章
解決在IDEA中創(chuàng)建多級(jí)package的問(wèn)題
這篇文章主要介紹了解決在IDEA中創(chuàng)建多級(jí)package的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02Java深入講解instanceof關(guān)鍵字的使用
instanceof 是 Java 的一個(gè)二元操作符,類(lèi)似于 ==,>,< 等操作符。instanceof 是 Java 的保留關(guān)鍵字。它的作用是測(cè)試它左邊的對(duì)象是否是它右邊的類(lèi)的實(shí)例,返回 boolean 的數(shù)據(jù)類(lèi)型2022-05-05Mybatis 級(jí)聯(lián)刪除的實(shí)現(xiàn)
這篇文章主要介紹了Mybatis 級(jí)聯(lián)刪除的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11Java/Android 實(shí)現(xiàn)簡(jiǎn)單的HTTP服務(wù)器
這篇文章主要介紹了Java/Android 如何實(shí)現(xiàn)簡(jiǎn)單的HTTP服務(wù)器,幫助大家更好的進(jìn)行功能測(cè)試,感興趣的朋友可以了解下2020-10-10Servlet的5種方式實(shí)現(xiàn)表單提交(注冊(cè)小功能),后臺(tái)獲取表單數(shù)據(jù)實(shí)例
這篇文章主要介紹了Servlet的5種方式實(shí)現(xiàn)表單提交(注冊(cè)小功能),后臺(tái)獲取表單數(shù)據(jù)實(shí)例,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05Java Mybatis中的 ${ } 和 #{ }的區(qū)別使用詳解
這篇文章主要介紹了Mybatis中的 ${ } 和 #{ }的區(qū)別使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07