不像arrayblockingqueue或linkedlistblockingqueue,synchronousqueue内部并沒有資料緩
存空間,你不能調用peek()方法來看隊列中是否有資料元素,因為資料元素隻有當你試着取走的時候才可能存在,不取走而隻想偷窺一下是不行的,當然周遊
這個隊列的操作也是不允許的。隊列頭元素是第一個排隊要插入資料的線程,而不是要交換的資料。資料是在配對的生産者和消費者線程之間直接傳遞的,并不會将資料緩沖資料到隊列中。可以這樣來了解:生産者和消費者互相等待對方,握手,然後一起離開。
synchronousqueue的一個使用場景是線上程池裡。executors.newcachedthreadpool()就使用了
synchronousqueue,這個線程池根據需要(新任務到來時)建立新的線程,如果有空閑線程則會重複使用,線程空閑了60秒後會被回收。
阻塞隊列的實作方法有許多:
阻塞算法實作通常在内部采用一個鎖來保證多個線程中的put()和take()方法是串行執行的。采用鎖的開銷是比較大的,還會存在一種情況是線程
a持有線程b需要的鎖,b必須一直等待a釋放鎖,即使a可能一段時間内因為b的優先級比較高而得不到時間片運作。是以在高性能的應用中我們常常希望規避鎖
的使用。
經典同步隊列實作采用了三個信号量,代碼很簡單,比較容易了解:
在多核機器上,上面方法的同步代價仍然較高,作業系統排程器需要上千個時間片來阻塞或喚醒線程,而上面的實作即使在生産者put()時已經有一個消費者在等待的情況下,阻塞和喚醒的調用仍然需要。
java 5的實作相對來說做了一些優化,隻使用了一個鎖,使用隊列代替信号量也可以允許釋出者直接釋出資料,而不是要首先從阻塞在信号量處被喚醒。
算法。性能比java5的實作有較大提升。競争機制支援公平和非公平兩種:非公平競争模式使用的資料結構是後進先出棧(lifo
stack);公平競争模式則使用先進先出隊列(fifo
queue),性能上兩者是相當的,一般情況下,fifo通常可以支援更大的吞吐量,但lifo可以更大程度的保持線程的本地化。
代碼實作裡的dual queue或stack内部是用連結清單(linkedlist)來實作的,其節點狀态為以下三種情況:
持有資料 – put()方法的元素
持有請求 – take()方法
空
這個算法的特點就是任何操作都可以根據節點的狀态判斷執行,而不需要用到鎖。
其核心接口是transfer,生産者的put或消費者的take都使用這個接口,根據第一個參數來差別是入列(棧)還是出列(棧)。
transferqueue實作如下(摘自java 6源代碼),入列和出列都基于spin和cas方法: