緊密相關的同步化隊列現在來看一種緊密相關的同步方式。一個或多個生產者線程生產數據,並由一個或多個消費者線程按照先進先出的次序來獲取。但是,生產者與消費者之間必須相互回合,即向隊列中放入一個元素的生產者應阻塞直到該元素被另外一個消費者取出,反之亦然。其實現的僞代碼描述如下:
public class SynchronousQueue<T>{
T item=null;
boolean enqueuing;
Lock lock;
Condition condition;
public void enq(T value){
lock.lock();
try{
while(enqueuing){
condition.await();
}
enqueuing=true;
item=value;
condition.signalAll();
while(item!=null)
condition.await();
enqueuing=false;
condition.signalAll();
}finally{
lock.unlock();
}
}
public T deq(){
lock.lock();
try{
while(item==null)
condition.await();
T t=item;
item=null;
condition.signalAll();
return t;
}finally{
lock.unlock();
}
}
}
這是一種基於管程的同步隊列實現。由於這個隊列設計非常簡單,所以他的同步代價很高。在每個線程可能喚醒另一個線程的時間點,無論是入隊者還是出隊者都會喚醒所有的等待線程,從而喚醒的次數是等待線程數目的平方。儘管可以使用條件對象來減少喚醒次數,但由於仍需要阻塞每次調用,所以開銷很大。
爲了減少同步隊列的同步開銷,我們考慮另外一種同步隊列的實現,在該實現中將入隊與出隊分兩步完成。如出隊從一個空隊列刪除元素的過程爲,第一步,他將一個保留對象放入隊列,表示該出隊者這在等待一個準備與之會合的入隊者。然後,出隊者在這個保留對象的flag標誌上自旋等待。第二步,當一個入隊者發現該保留時,他通過存放一個元素並設置保留對象的flag來通知出隊者完成保留。同樣,入隊者也能夠通過創建自己的保留對象,並在保留對象的flag標誌上自旋來等待會合同伴。在任意時刻,隊列本身或者包含出隊者的保留對象或者包含入隊者的保留對象,或者爲空。因此操作隊列的線程共有4種交互對象,即入隊者線程、入隊者保留、出隊者線程、出隊者保留,由於需要進行緊密的同步,因此他們之間的對應關係如下:
入隊者線程————>出隊者保留;
出隊者線程————>入隊者保留;
即入隊者線程要協助處理出隊者保留,同樣出隊者線程要協助處理入隊者保留。這種結構稱爲雙重數據結構,其核心是方法是通過兩個步驟來生效的:保留和完成。該結構具有很多很好的性質。首先,正在等待的線程可以在一個本地緩存標誌上自旋,而這時可擴展性的基礎。其次,他很自然的保證了公平性。保留按照他們到達的次序來排隊,從而保證請求也按照同樣的次序完成,因此這種結構是可以線性化的,因爲每個部分方法調用在他完成時是可以排序的。
該隊列結構可以用節點組成的鏈表來實現,其中節點或者表示一個等待出隊的元素或者表示一個等待完成的保留,由節點的Type域指定。任何時候,所有的隊列節點都應具備相同的類型,即或者全部是等待出隊的元素,或者全部是等待完成的保留。當一個元素入隊時,節點的item域存放該元素;當元素出隊時,節點的item域被重新設置爲null。當一個保留入隊時,節點的item域爲null;當保留被一個入隊者完成時,節點的item域被重新設置爲一個元素。
因此首先定義一個Java枚舉,來表示節點的類型,然後定義鏈表的節點元素,在整個實現過程中,依然基於Java的原子化操作來實現併發控制,所以源代碼如下所示:
public enum NodeType {
ITEM,RESERVATION;
}
其中,ITEM代表節點元素,RESERVATION代表保留對象。
mport java.util.concurrent.atomic.AtomicReference;
public class SynNode<E> {
volatile NodeType type;//節點類型
//節點元素,元素值爲Java泛化類型
volatile AtomicReference<E> item;
volatile AtomicReference<SynNode<E>> next;
SynNode(E eitem,NodeType etype){
item=new AtomicReference<E>(eitem);
next=new AtomicReference<SynNode<E>>(null);
type=etype;
}
}
接下來定義隊列的主體實現,其中主要包括:入隊和出對操作。
import java.util.concurrent.atomic.AtomicReference;
public class SynchronousDualQueue<E> {
private AtomicReference<SynNode<E>> head,tail;//頭尾哨兵節點
public SynchronousDualQueue(){
//初始化空隊列,創建一個具有任意值的節點,並讓頭尾指針指向該節點
SynNode<E> snode=new SynNode<E>(null,NodeType.ITEM);
head=newAtomicReference<SynNode<E>>(snode);
tail=newAtomicReference<SynNode<E>>(snode);
}
//入隊操作
public void enq(E item){
//創建將被入隊的新節點
SynNode<E> offer=new SynNode<E>(item,NodeType.ITEM);
while(true){
//獲取頭尾哨兵節點
SynNode<E> t=tail.get();
SynNode<E> h=head.get();
//檢驗隊列是否爲空或者是否包含已入隊元素的出隊保留
if(t==h||t.type==NodeType.ITEM){
//讀取tail節點的後繼
SynNode<E> n=t.next.get();
//判斷讀取的tail值是一致的,即tail的狀態不會被併發線程更改
if(t==tail.get()){
//如果tail域沒有指向隊列的最後一個節點,則嘗試推進tail,並重新開始
if(n!=null){
//嘗試推進tail
tail.compareAndSet(t,n);
}
//如果tail域就是隊列最後一個元素,那麼嘗試將tail的後繼指向新增節點,
//即將新增節點掛接到隊尾
elseif(t.next.compareAndSet(n,offer)){
//如果成功掛接,那麼嘗試推進tail域,使其指向新增節點
tail.compareAndSet(t,offer);
//自旋等待,等待一個出隊者通過設置該節點的item域爲null來通知該元素已
//經出隊。這是因爲這是一個緊密同步的隊列,入隊元素必須等待使用它的出隊者
while(offer.item.get()==item);
//一旦出隊成功,就嘗試移動頭指針,以便進行清理。這是因爲出隊是從頭節點
//出隊。通過將新增節點設置爲head哨兵節點,實現對被使用節點的清理
h=head.get();
if(offer==h.next.get()){
head.compareAndSet(h,offer);
}
return;
}
}
}
//如果存在正在等待完成的出隊者的保留,那麼就找出一個保留並完成它
else{
//讀取head的後繼節點
SynNode<E> node=h.next.get();
//判斷讀到的值是一致的,即運行過程中狀態的一致性不能被併發線程所破壞,
//主要通過尾指針是否一致、頭指針是否一致、以及首節點是否爲空來判斷,
//這些狀態都可能在運行中被併發線程改變,因此只要有一個狀態被改變,
//即認爲整體狀態被破壞了,需要重新開始
if(t!=tail.get()||h!=head.get()||node==null){
continue;
}
//如果狀態一致,那麼嘗試着將節點的item域從null改爲要入隊的元素。
//因爲出隊者保留等待的是一個入隊者,所以要將item域由null置爲入隊元素的item
boolean success=node.item.compareAndSet(null,item);
//不管上一步是否成功,都嘗試推進head
head.compareAndSet(h,node);
//如果推進head成功,該方法返回,否則重試
if(success){
return;
}
}
}
}
//出隊操作,其操作邏輯與入隊基本相似,在此不再贅述
public E deq(){
E result=null;
while(true){
SynNode<E> h=head.get();
SynNode<E> t=tail.get();
if(h==t || h.type==NodeType.ITEM){
SynNode<E> n=h.next.get();
if(h==head.get()){
if(n==null){
tail.compareAndSet(t,new SynNode<E>(null,NodeType.RESERVATION));
}else if(head.compareAndSet(h,n)){
while(n.item.get()==null);
t=tail.get();
if(n==t.next.get()){
result=n.item.get();
tail.compareAndSet(t,n);
}
return result;
}
}
}
else{
SynNode<E> node=t.next.get();
result=node.item.get();
if(t!=tail.get()||h!=head.get()||node==null){
continue;
}
boolean success=node.item.compareAndSet(result,null);
tail.compareAndSet(t,node);
if(success){
return result;
}
}
}
}
}