本文研究的主要是Java並發之條件阻塞Condition的應用示例代碼,具體如下。
Condition將Object監視器方法(wait、notify 和notifyAll)分解成截然不同的對象,以便通過將這些對象與任意Lock實現組合使用,為每個對象提供多個等待set(wait-set)。其中,Lock 替代了synchronized方法和語句的使用,Condition替代了Object監視器方法的使用。
由於Condition可以用來替代wait、notify等方法,所以可以對比著之前寫過的線程間通信的代碼來看,再來看一下原來那個問題:
有兩個線程,子線程先執行10次,然後主線程執行5次,然後再切換到子線程執行10,再主線程執行5次……如此往返執行50次。
之前用wait和notify來實現的,現在用Condition來改寫一下,代碼如下:
public class ConditionCommunication {public static void main(String[] args) {Business bussiness = new Business();new Thread(new Runnable() {// 開啟一個子線程@Override public void run() {for (int i = 1; i <= 50; i++) {bussiness.sub(i);}}}).start();// main方法主線程for (int i = 1; i <= 50; i++) {bussiness.main(i);}}}class Business {Lock lock = new ReentrantLock();Condition condition = lock.newCondition();//Condition是在具體的lock之上的private Boolean bShouldSub = true;public void sub(int i) {lock.lock();try {while (!bShouldSub) {try {condition.await();//用condition來調用await方法}catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int j = 1; j <= 10; j++) {System.out.println("sub thread sequence of " + j + ", loop of " + i);}bShouldSub = false;condition.signal();//用condition來發出喚醒信號,喚醒某一個}finally {lock.unlock();}}public void main(int i) {lock.lock();try {while (bShouldSub) {try {condition.await();//用condition來調用await方法}catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int j = 1; j <= 10; j++) {System.out.println("main thread sequence of " + j + ", loop of " + i);}bShouldSub = true;condition.signal();//用condition來發出喚醒信號麼,喚醒某一個}finally {lock.unlock();}}}從代碼來看,Condition的使用時和Lock一起的,沒有Lock就沒法使用Condition,因為Condition是通過Lock來new出來的,這種用法很簡單,只要掌握了synchronized和wait、notify的使用,完全可以掌握Lock和Condition的使用。
上面使用Lock和Condition來代替synchronized和Object監視器方法實現了兩個線程之間的通信,現在再來寫個稍微高級點應用:模擬緩衝區的阻塞隊列。
什麼叫緩衝區呢?舉個例子,現在有很多人要發消息,我是中轉站,我要幫別人把消息發出去,那麼現在我就需要做兩件事,一件事是接收用戶發過來的消息,並按順序放到緩衝區,另一件事是從緩衝區中按順序取出用戶發過來的消息,並發送出去。
現在把這個實際的問題抽像一下:緩衝區即一個數組,我們可以向數組中寫入數據,也可以從數組中把數據取走,我要做的兩件事就是開啟兩個線程,一個存數據,一個取數據。但是問題來了,如果緩衝區滿了,說明接收的消息太多了,即發送過來的消息太快了,我另一個線程還來不及發完,導致現在緩衝區沒地方放了,那麼此時就得阻塞存數據這個線程,讓其等待;相反,如果我轉發的太快,現在緩衝區所有內容都被我發完了,還沒有用戶發新的消息來,那麼此時就得阻塞取數據這個線程。
好了,分析完了這個緩衝區的阻塞隊列,下面就用Condition技術來實現一下:
class Buffer {final Lock lock = new ReentrantLock();//定義一個鎖final Condition notFull = lock.newCondition();//定義阻塞隊列滿了的Conditionfinal Condition notEmpty = lock.newCondition();//定義阻塞隊列空了的Conditionfinal Object[] items = new Object[10];//為了下面模擬,設置阻塞隊列的大小為10,不要設太大int putptr, takeptr, count;//數組下標,用來標定位置的//往隊列中存數據public void put(Object x) throws InterruptedException {lock.lock();//上鎖try {while (count == items.length) {System.out.println(Thread.currentThread().getName() + " 被阻塞了,暫時無法存數據!");notFull.await();//如果隊列滿了,那麼阻塞存數據這個線程,等待被喚醒}//如果沒滿,按順序往數組中存items[putptr] = x;if (++putptr == items.length) //這是到達數組末端的判斷,如果到了,再回到始端putptr = 0;++count;//消息數量System.out.println(Thread.currentThread().getName() + " 存好了值: " + x);notEmpty.signal();//好了,現在隊列中有數據了,喚醒隊列空的那個線程,可以取數據啦}finally {lock.unlock();//放鎖}}//從隊列中取數據public Object take() throws InterruptedException {lock.lock();//上鎖try {while (count == 0) {System.out.println(Thread.currentThread().getName() + " 被阻塞了,暫時無法取數據!");notEmpty.await();//如果隊列是空,那麼阻塞取數據這個線程,等待被喚醒}//如果沒空,按順序從數組中取Object x = items[takeptr];if (++takeptr == items.length) //判斷是否到達末端,如果到了,再回到始端takeptr = 0;--count;//消息數量System.out.println(Thread.currentThread().getName() + " 取出了值: " + x);notFull.signal();//好了,現在隊列中有位置了,喚醒隊列滿的那個線程,可以存數據啦return x;}finally {lock.unlock();//放鎖}}}這個程序很經典,我從官方JDK文檔中拿出來的,然後加了註釋。程序中定義了兩個Condition,分別針對兩個線程,等待和喚醒分別用不同的Condition來執行,思路很清晰,程序也很健壯。可以考慮一個問題,為啥要用兩個Codition呢?之所以這麼設計肯定是有原因的,如果用一個Condition,現在假設隊列滿了,但是有2個線程A和B同時存數據,那麼都進入了睡眠,好,現在另一個線程取走一個了,然後喚醒了其中一個線程A,那麼A可以存了,存完後,A又喚醒一個線程,如果B被喚醒了,那就出問題了,因為此時隊列是滿的,B不能存的,B存的話就會覆蓋原來還沒被取走的值,就因為使用了一個Condition,存和取都用這個Condition來睡眠和喚醒,就亂了套。到這裡,就能體會到這個Condition的用武之地了,現在來測試一下上面的阻塞隊列的效果:
public class BoundedBuffer {public static void main(String[] args) {Buffer buffer = new Buffer();for (int i = 0; i < 5; i ++) {//開啟5個線程往緩衝區存數據new Thread(new Runnable() {@Override public void run() {try {buffer.put(new Random().nextint(1000));//隨機存數據}catch (InterruptedException e) {e.printStackTrace();}}}).start();}for (int i = 0; i < 10; i ++) {//開啟10個線程從緩衝區中取數據new Thread(new Runnable() {@Override public void run() {try {buffer.take();//從緩衝區取數據}catch (InterruptedException e) {e.printStackTrace();}}}).start();}}}我故意只開啟5個線程存數據,10個線程取數據,就是想讓它出現取數據被阻塞的情況發生,看運行的結果:
Thread-5 被阻塞了,暫時無法取數據!
Thread-10 被阻塞了,暫時無法取數據!
Thread-1 存好了值: 755
Thread-0 存好了值: 206
Thread-2 存好了值: 741
Thread-3 存好了值: 381
Thread-14 取出了值: 755
Thread-4 存好了值: 783
Thread-6 取出了值: 206
Thread-7 取出了值: 741
Thread-8 取出了值: 381
Thread-9 取出了值: 783
Thread-5 被阻塞了,暫時無法取數據!
Thread-11 被阻塞了,暫時無法取數據!
Thread-12 被阻塞了,暫時無法取數據!
Thread-10 被阻塞了,暫時無法取數據!
Thread-13 被阻塞了,暫時無法取數據!
從結果中可以看出,線程5和10搶先執行,發現隊列中沒有,於是就被阻塞了,睡在那了,直到隊列中有新的值存入才可以取,但是它們兩運氣不好,存的數據又被其他線程給搶先取走了,哈哈……可以多運行幾次。如果想要看到存數據被阻塞,可以將取數據的線程設置少一點,這裡我就不設了。
還是原來那個題目,現在讓三個線程來執行,看一下題目:
有三個線程,子線程1先執行10次,然後子線程2執行10次,然後主線程執行5次,然後再切換到子線程1執行10次,子線程2執行10次,主線程執行5次……如此往返執行50次。
如過不用Condition,還真不好弄,但是用Condition來做的話,就非常方便了,原理很簡單,定義三個Condition,子線程1執行完喚醒子線程2,子線程2執行完喚醒主線程,主線程執行完喚醒子線程1。喚醒機制和上面那個緩衝區道理差不多,下面看看代碼吧,很容易理解。
public class ThreeConditionCommunication {public static void main(String[] args) {Business bussiness = new Business();new Thread(new Runnable() {// 開啟一個子線程@Override public void run() {for (int i = 1; i <= 50; i++) {bussiness.sub1(i);}}}).start();new Thread(new Runnable() {// 開啟另一個子線程@Override public void run() {for (int i = 1; i <= 50; i++) {bussiness.sub2(i);}}}).start();// main方法主線程for (int i = 1; i <= 50; i++) {bussiness.main(i);}}static class Business {Lock lock = new ReentrantLock();Condition condition1 = lock.newCondition();//Condition是在具體的lock之上的Condition condition2 = lock.newCondition();Condition conditionMain = lock.newCondition();private int bShouldSub = 0;public void sub1(int i) {lock.lock();try {while (bShouldSub != 0) {try {condition1.await();//用condition來調用await方法}catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int j = 1; j <= 10; j++) {System.out.println("sub1 thread sequence of " + j + ", loop of " + i);}bShouldSub = 1;condition2.signal();//讓線程2執行}finally {lock.unlock();}}public void sub2(int i) {lock.lock();try {while (bShouldSub != 1) {try {condition2.await();//用condition來調用await方法}catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int j = 1; j <= 10; j++) {System.out.println("sub2 thread sequence of " + j + ", loop of " + i);}bShouldSub = 2;conditionMain.signal();//讓主線程執行}finally {lock.unlock();}}public void main(int i) {lock.lock();try {while (bShouldSub != 2) {try {conditionMain.await();//用condition來調用await方法}catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int j = 1; j <= 5; j++) {System.out.println("main thread sequence of " + j + ", loop of " + i);}bShouldSub = 0;condition1.signal();//讓線程1執行}finally {lock.unlock();}}}}代碼看似有點長,但是是假象,邏輯非常簡單。關於線程中的Condition技術就總結這麼多吧。
以上就是本文關於Java並發之條件阻塞Condition的應用代碼示例的全部內容,希望對大家有所幫助。感興趣的朋友可以繼續參閱本站其他相關專題,如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!