一、Condition接口簡介

在上述兩篇文章中講解AQS的時候,我們已經知道了同步隊列AQS的內部類ConditionObject實現了Condition接口,使用ReentrantLock和ReentrantReadWriteLock的內部類Sync我們可以通過newCondition() 方法創建一個或多個ConditionObject對象。

在使用synchronized作爲同步的時候,可以使用任意的Java對象作爲鎖,這是因爲任意的一個Java對象,都擁有一組監視器方法,這些監視器方法是定義在超類Object中的,主要包括:wait、notify、notifyAll這些方法,這些方法與synchronized關鍵字配合實現等待/通知模式。

Condition接口也提供了類似object的監視器方法,與Lock配合使用也可以實現等待/通知模式,雖然如此,但是兩者在使用方式以及功能功能上還是有些許差別的,主要差別如下:

Java多線程編程-(18)-等待/通知模式接口Condition接口深入分析

Condition接口定義:

Java多線程編程-(18)-等待/通知模式接口Condition接口深入分析

可以看到基本和Object超類中定義的差不多。

各接口的含義如下:

Java多線程編程-(18)-等待/通知模式接口Condition接口深入分析

二、Condition接口使用案例

示例代碼如下:

Java多線程編程-(18)-等待/通知模式接口Condition接口深入分析

測試代碼如下:

Java多線程編程-(18)-等待/通知模式接口Condition接口深入分析

看過《Java多線程編程-(5)-線程間通信機制的介紹與使用》 這篇文章的小夥伴都應該還記得,我們使用wait和notify實現了一個阻塞隊列,現在我們使用Condition對象搞一些事情,使用Condition把這個阻塞隊列重寫一下,代碼如下:

Java多線程編程-(18)-等待/通知模式接口Condition接口深入分析

Java多線程編程-(18)-等待/通知模式接口Condition接口深入分析

Java多線程編程-(18)-等待/通知模式接口Condition接口深入分析

Java多線程編程-(18)-等待/通知模式接口Condition接口深入分析

三、Condition接口實現原理

ConditionObject實現了Condition接口,是AQS的內部類,因爲Condition的操作需要獲取相關聯的鎖,所以作爲同步器的內部類是一個比較合理的方式。每一個Condition對象都包含一個等待隊列,該隊列是Condition實現等待通知機制的關鍵。

和synchronized一樣,在調用wait和notify等方法之前都必須要先獲取鎖,同樣使用Condition對象的await和signal方法的時候也是要先獲取到鎖!

1、等待隊列

等待隊列是一個FIFO的隊列,在隊列中的每一個節點都包含一個線程的引用,該線程就是在Condition對象上等待的線程,如果一個線程調用了Condition.await() 方法,那麼該線程將會釋放鎖,構造成節點加入等待隊列並進入等待狀態。這裏的節點Node使用的是AQS中定義的Node。也就是說AQS中的同步隊列和Condition的等待隊列使用的節點類型都是AQS中定義的Node內部類(AbstractQueuedSynchronizer.Node)。

一個Condition對象包含一個等待隊列,Condition擁有首節點和尾節點。當前線程調用Condition.await() 方法,將會以當前線程構造節點,並將該節點從尾部加入到等待隊列,等待隊列的基本結構如下圖:

Java多線程編程-(18)-等待/通知模式接口Condition接口深入分析

如上圖可知,Condition擁有首尾節點的引用,而新增節點只需要將原有的尾節點nextWaiter指向它,並且更新尾節點即可。上述節點引用更新的過程並沒有使用到CAS保證,這是因爲當前線程調用await() 方法的時候必定是獲取了鎖的線程,也就是說該過程是由鎖來保證線程安全的。

我們知道在使用synchronized的時候,是使用的對象監視器模型的,即在Object的監視器模型上,一個對象擁有一個同步隊列和等待隊列,而Lock可以擁有一個同步隊列和多個等待隊列,這是因爲通過lock.newCondition() 可以創建多個Condition條件,而這多個Condition對象都是在同一個鎖的基礎上創建的,在同一時刻也只能由一個線程獲取到該鎖。

Lock模式下同步隊列和等待隊列的對應關係如下圖:

Java多線程編程-(18)-等待/通知模式接口Condition接口深入分析

又因爲Condition的實現是AQS的內部類,因此每個Condition對象都可以訪問AQS同步器提供的方法,相當於每個Condition都擁有所屬同步器AQS的引用。

2、等待的實現

當前線程調用Condition.await() 方法的時候,相當於將當前線程從同步隊列的首節點移動到Condition的等待隊列中,並釋放鎖,同時線程變爲等待狀態。

當前線程加入到等待隊列的過程如下:

Java多線程編程-(18)-等待/通知模式接口Condition接口深入分析

可以看出同步隊列的首節點並不是直接加入到等待隊列的尾節點,而是封裝成等待隊列的節點才插入到等待隊列的尾部的。

3、通知的實現

調用當前線程的Condition.signal() 方法,將會喚醒在等待隊列中等待時間最長的節點也就是首節點,在喚醒節點之前,會將該節點移到同步隊列中。

節點從等待隊列加入到同步隊列的過程如下:

Java多線程編程-(18)-等待/通知模式接口Condition接口深入分析

通過調用同步器的方法將等待隊列中的頭結點線程安全的移到同步隊列的尾節點,當前線程在使用LockSupport喚醒該節點的線程。

被喚醒後的線程,將會從await() 方法中的while循環中退出,進而調用同步器的方法加入到獲取同步狀態的競爭中。

成功獲取同步狀態之後,被喚醒的線程從先前調用的await飯發個返回,此時該線程已經成功的獲取了鎖。

Condition的signalAll() 方法,相當於對等待隊列中的每一個節點均執行一次signal()方法,效果就是將等待隊列中的所有節點全部移到同步隊列中,並喚醒每個節點的線程。

參考文章:

1、部分內容和截圖來自《Java併發編程的藝術》

2、http://blog.csdn.net/ghsau/article/details/7481142

3、http://ifeve.com/understand-condition/

4、http://www.cnblogs.com/zhengbin/p/6420984.html


來源:Java後端技術

相關文章