您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關java并發等待條件的示例分析,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
前言
前面介紹了排它鎖,共享鎖的實現機制,本篇繼續學習AQS中的另外一個內容-Condition。想必學過java的都知道Object.wait和Object.notify,同時也應該知曉這兩個方法的使用離不開synchronized關鍵字。synchronized是jvm級別提供的同步原語,它的實現機制隱藏在jvm實現中。作為Lock系列功能中的Condition,就是用來實現類似 Object.wait和Object.notify 對應功能的。
使用場景
為了更好的理解Lock和Condition的使用場景,下面我們先來實現這樣一個功能:有多個生產者,多個消費者,一個產品容器,我們假設容器最多可以放3個產品,如果滿了,生產者需要等待產品被消費,如果沒有產品了,消費者需要等待。我們的目標是一共生產10個產品,最終消費10個產品,如何在多線程環境下完成這一挑戰呢?下面是我簡單實現的一個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 { // 生產 和 消費 的最大總數 public static int totalCount = 10; // 已經生產的產品數 public static volatile int hasProduceCount = 0; // 已經消費的產品數 public static volatile int hasConsumeCount = 0; // 容器最大容量 public static int containerSize = 3; // 使用公平策略的可重入鎖,便于觀察演示結果 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>(); // 用于標識產品 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{ // 容器滿了,需要等待非滿條件 while(container.size() >= containerSize){ notFull.await(); } // 到這里表明容器未滿,但需要再次判斷是否已經完成了任務 if(hasProduceCount >= totalCount){ System.out.println(Thread.currentThread().getName()+" producer exit"); return ; } int product = idGenerator.incrementAndGet(); // 把生產出來的產品放入容器 container.addLast(product); System.out.println(Thread.currentThread().getName() + " product " + product); hasProduceCount++; // 通知消費線程可以去消費了 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 ; } // 一直等待有產品了,再繼續往下消費 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++; // 通知生產線程可以繼續生產產品了 notFull.signal(); } catch (InterruptedException e) { }finally{ lock.unlock(); } } } } }
一次執行結果如下:
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.
從結果可以發現已經達到我們的目的了。
深入理解Condition的實現原理
上面的示例只是為了展示 Lock結合Condition可以實現的一種經典場景,在有了感性的認識之后,我們將一步一步來觀察Lock和Condition是如何協作完成這一任務的,這也是本篇的核心內容。
為了更好的理解和演示這一個過程,我們使用到的鎖是使用公平策略模式的,我們會使用上面例子運作的流程。我們會使用到3個生產線程,3個消費線程,分別表示 p1、p2、p3和c1、c2、c3。
Condition的內部實現是使用節點鏈來實現的,每個條件實例對應一個節點鏈,我們有notEmpty 和 notFull 兩個條件實例,所以會有兩個等待節點鏈。
一切準備就緒 ,開始我們的探索之旅。
1、線程c3執行,然后發現沒有產品可以消費,執行 notEmpty.await,進入等待隊列中等候。
2、線程c2和線程c1執行,然后發現沒有產品可以消費,執行 notEmpty.await,進入等待隊列中等候。
3、 線程 p1 啟動,得到了鎖,p1開始生產產品,這時候p3搶在p2之前,執行了lock操作,結果p2和p3都處于等待狀態,入同步隊列等待。<喎?"/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxpbWcgYWx0PQ=="這里寫圖片描述" src="/uploadfile/Collfiles/20160912/20160912092710536.png" title="\" />
注意,本例中我們使用的是公平策略模式下的排它鎖,由于p3搶先執行取鎖操作,所以雖然p2和p3都被阻塞了,但是p3會優先被喚醒 。
4、這會,p1生產完畢,通知 not empty等待隊列,可以喚醒一個等待線程節點了,然后釋放了鎖,釋放鎖會導致p3被喚醒,然后p1進入下一個循環,進入同步隊列。
事情開始變得有趣了,p1執行一次生產后,執行了 notEmpty.signal,其效果就是把 not empty等待列表中的頭節點,即c3節點移到同步等待列隊中,重新參與搶占鎖。
5、p3生產完了產品后,繼續notEmpty.signal,同時釋放鎖,釋放鎖后會喚醒p2線程,然后p3在下一輪嘗試獲取鎖的時候,再次入隊。
6、接著,p2繼續生產,生產后執行 notEmpty.signal,同時釋放鎖,釋放鎖后喚醒c3線程,然后p2在下一輪嘗試取鎖的時候,入列。
7、c3進行消費,你可以看到,現在 not empty等待列隊中已經沒有等待節點了,由于我們使用的是公平策略排它鎖,這就會導致同步隊列中的節點一個接著一個執行,而目前同步隊列中的節點排列為一生產,一消費,這不難可以知道,接下來代碼已經不會進入 wait條件了,所以一個一個輪流執行就是,比如c3,執行完了,繼續notFull.signal(); 然后釋放鎖,入隊,這里要明白,notFull.signal();這句代碼其實沒有作用了,因為 not full等待隊列中沒有任何等待線程節點。 c3執行后,狀態如下圖所示:
關于“java并發等待條件的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。