JAVA 1.7並發之LinkedTransferQueue原理理解


昨天剛看完BlockingQueue覺得好高級啊,今天掃到1.7就發現了升級版。。。。

如果對內容覺得不夠充分,可以去看http://www.cs.rochester.edu/u/scott/papers/2009_Scherer_CACM_SSQ.pdf 

就是作者的論文啦,純英文。。。比較難啃,但是我覺得邏輯上比看代碼容易理解,其實代碼什么u啊h啊看得很混

 

LinkedTransferQueue

 

起源: 我覺得是這樣的,之前的BlockingQueue是對 讀取 或者 寫入 鎖定整個隊列,所以在比較繁忙的時候,各種鎖比較耗時

而當時有一個SynchronizedQueue其實不能叫Queue,因為只能放一個物件,要么有一個物件在等人拿,要么有一個空等人放

根據這個原理,誕生了LinkedTransferQueue,利用CompareAndSwap進行一個無阻塞的隊列,針對每一個操作進行處理樣大家就不用搶得那么辛苦了

 

數據結構

在類的內部保持着一個棧,基本單位是node,根據 hasData區分里面有兩種元素,要么是 Data 要么是 Reservation,不會同時存在

並且有一個變量head指向最前面的node,沒東西則是null

Node

{

  isData    是不是數據,是的話item放具體東西

  item   如果不是數據則為null

  next   下一個節點

  waiter  如果不是數據則是reservation,有一個線程在等待

}

 

過程:

整個存取過程分成兩部分

 1:MATCH(原節點,新節點)

for (;;) {                            // restart on append race

            for (Node h = head, p = h; p != null;) { // 如果頭結點為空則跳過,非空進去找第一個可用節點
                boolean isData = p.isData;
                Object item = p.item;
                if (item != p && (item != null) == isData) { // 判斷原節點可用性,如data的item應該是數值,如果是null則表明用過了
                    if (isData == haveData)   // 兩個節點是相同類型,不用match了,去下一步
                        break;
                    if (p.casItem(item, e)) { // 節點不同類型,match成功,更改原節點item,表明不可用
                        for (Node q = p; q != h;) {//什么,我居然不是head節點了?我要讓它指向我!
                            Node n = q.next;  // update by 2 unless singleton
                            if (head == h && casHead(h, n == null ? q : n)) {
                                h.forgetNext();
                                break;
                            }                 // advance and retry
                            if ((h = head)   == null ||
                                (q = h.next) == null || !q.isMatched())
                                break;        // unless slack < 2
                        }
                        LockSupport.unpark(p.waiter);//根據原節點的類型,reservation則叫人收貨,data則叫null收貨
                        return LinkedTransferQueue.<E>cast(item);//根據原節點的類型,reservation則返回null,data則返回數據
                    }
                }
                Node n = p.next;//下一個節點
                p = (p != n) ? n : (h = head); // Use head if p offlist
            }

  重點是找出第一個可用節點,如果是null則跳過,如果與進來的節點相同(本來就有data,還放data)也跳過,如果不同(本來是data,現在是reservation,返回data值 / 本來是reservation,現在是data,叫人來收貨,返回reservation值=空)

 2:處理節點

if (how != NOW) {                 // No matches available
                if (s == null)
                    s = new Node(e, haveData);
                Node pred = tryAppend(s, haveData);//嘗試添加新node
                if (pred == null)
                    continue retry;           // 不成功則重試整個過程
                if (how != ASYNC)
                    return awaitMatch(s, pred, e, (how == TIMED), nanos);//根據參數,等不等別人放數據,拿數據,等多久
            }
            return e; // not waiting

  MATCH失敗了才會進入這個環節,把新節點放進棧內,並根據參數決定立刻返回或者等待返回

EXAMPLES

1:Head->Data    Input->Data

Match:      根據他們的屬性 發現 cannot match ,因為是同類的

處理節點:   所以把新的data放在原來的data后面,然后head往后移一位,Reservation同理

HEAD=DATA->DATA

 

2:Head->Data    Input->Reservation  (取數據)

Match:      成功match,就把Data的item變為reservation的值(null,有主了),並且返回數據。

處理節點:  沒動,head還在原地

HEAD=DATA(用過)

 

3:Head->Reservation  Input->Data(放數據)

Match:       成功match,就把Reservation的item變為Data的值(有主了),並且叫waiter來取

處理節點:  沒動

HEAD=RESERVATION(用過)

 

總結:LinkedTransferQueue通過CAS嘗試放入data或增加reservation。

消耗小於把整個隊列鎖掉,但是在並發特別高的情況下大家搶着嘗試一樣會影響速度

至於為什么跨過了1.6到1.7這個類才出現我覺得有點神奇

 

簡單用法介紹------------------------------------------------------------------------------------------------------

存:

put();   放元素進去隊列,注意隊列是可以無限長的

add();   同上

transfer();  這個是重點,如果隊列中有人發現有人在等,則直接給那個人(有一個參數waiter指定了在等的線程)

如果沒人在等,就放進隊列

取:

poll();  立即返回,如果沒有元素就是空

take(); 如果沒有元素,那就等

PS:最好是用poll然后自己處理空的狀況,如果全是take然后又遲遲沒有東西,那就一堆內存在等了。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM