1. FutureTask的get方法靠什么機制來阻塞
看其get方法源碼:
/** * @throws CancellationException {@inheritDoc} */ public V get() throws InterruptedException, ExecutionException { return sync.innerGet(); }
不難發現,FutureTask是依靠其內部類java.util.concurrent.FutureTask.Sync<V>類來實現阻塞。
Sync又是實現了AbstractQueuedSynchronizer類。
private final class Sync extends AbstractQueuedSynchronizer
看都有誰實現了這個類:
里面有很多我們平時用到的,但是不怎么清楚其原理的類,原來都是靠實現AbstractQueuedSynchronizer來達到相應的同步機制。
AbstractQueuedSynchronizer又是靠什么來實現阻塞以及維持協調好各競爭線程間的資源分配的?看下一段。
2. 分析AbstractQueuedSynchronizer
從上面分析來看AbstractQueuedSynchronizer應該是JDK的concurrent包里比較重要的一個機制。用google先了解下他的面貌:
中文的blog的搜索結果說明有很多人對這個做了總結與學習,最下面框起來的一個pdf結果應該是對這個的一個論文。
論文中闡述了幾點比較關鍵的地方
1. 方法命名的問題
“
同步器一般包含兩種方法,一種是acquire,另一種是release。acquire操作阻塞調用的線程,直到或除非同步狀態允許其繼續執行。而release操作則是通過某種方式改變同步狀態,使得一或多個被acquire阻塞的線程繼續執行。
j.u.c包中並沒有對同步器的API做一個統一的定義。因此,有一些類定義了通用的接口(如Lock),而另外一些則定義了其專有的版本。因此在不同的 類中,acquire和release操作的名字和形式會各有不同。例 如:Lock.lock,Semaphore.acquire,CountDownLatch.await和FutureTask.get,在這個框架 里,這些方法都是acquire操作。但是,J.U.C為支持一系列常見的使用選項,在類間都有個一致約定。在有意義的情況下,每一個同步器都支持下面的 操作:
阻塞和非阻塞(例如tryLock)同步。
可選的超時設置,讓調用者可以放棄等待
通過中斷實現的任務取消,通常是分為兩個版本,一個acquire可取消,而另一個不可以。
”
2. 實現同步器的三個“組件”
“
為了實現上述操作,需要下面三個基本組件的相互協作:
a) 同步狀態的原子性管理;
b) 線程的阻塞與解除阻塞;
c) 隊列的管理;
”
怎么實現這三個部件,論文中有詳細介紹。
論文中提到“整個框架的關鍵就是如何管理被阻塞的線程的隊列”也就是對應上面三個“組件”的c部分。
AQS中使用了CLH隊列。
為什么采用CLH而不是MCS,因為CLH更容易實現取消與超時機制。
3. CLH隊列
談CLH隊列時,前面要先談很多基礎知識,不然直接閱讀上面的簡明介紹,會有點坡度。
4. AQS思維導圖