Java多線程 -- JUC包源碼分析16 -- Exchanger源碼分析__Exchanger

來源:互聯網
上載者:User

上一篇所講的SynchronousQueue,是2個線程之間單向的資料轉送,一個put,一個take。
而今天所講的Exchange,顧明思義,是雙向的資料轉送,2個線程在一個同步點,交換資料。

其使用方式,大致如下:

Exchange<String> exchange = new Exchange<String>(); //建1個多個線程共用的exchange對象//把exchange對象,傳給4個線程對象。每個線程在自己的run函數裡面,調用exchange,把自己的資料當參數傳進去,傳回值是另外一個線程調用exchange塞進去的參數。ThreadA a = new ThreadA(exchange);run(){   String other = exchange.exchange(self.data)  //沒有別的線程調用exchange的話,自己會阻塞在這。直到有別的線程調用exchange。}ThreadB b = new ThreadB(exchange);run(){   String other = exchange.exchange(self.data)}ThreadB c = new ThreadC(exchange);run(){   String other = exchange.exchange(self.data)}ThreadC d = new ThreadD(exchange);run(){   String other = exchange.exchange(self.data)}

在上面的例子中,4個線程並發的調用exchange,會兩兩互動資料。可能是A/B, C/D,也可能A/C, B/D,也可能是A/D, B/C。 Exchange實現

從簡單的角度來考慮,Exchange只需要一個互斥變數就夠了。因為可以限制,任何時候,只能有1對發生交換,不是多對,同時發生交換。

但為了提高並發度,Exchange內部用了多個變數,在其內部,稱之為Slot。

public class Exchanger<V> {    private static final class Slot extends AtomicReference<Object> {        long q0, q1, q2, q3, q4, q5, q6, q7, q8, q9, qa, qb, qc, qd, qe;    }   private volatile Slot[] arena = new Slot[CAPACITY];   。。。 }
關鍵技術點1:CacheLine填充

在上面的代碼中,Slot其實就是一個AtomicReference,其裡面的q0, q1,..qd那些變數,都是多餘的,不用的。那為什麼要添加這些多餘的變數呢。

是為了讓不同的Slot不要落在cpu的同一個CacheLine裡面。因為cpu從記憶體讀取資料的時候,不是一個位元組一個位元組的讀,而是按塊讀取,這裡的塊也就是“CacheLine”,一般一個CacheLine大小是64Byte。

保證一個Slot的大小 >= 64Byte,這樣更改一個Slot,就不會導致另外一個Slot的cpu cache失效,從而提高效能。

知道了Slot其實就是一個AtomicReference,下面討論第2個技術點 關鍵技術點2:鎖分離

同ConcurrentHashMap類型,Exchange沒有只定義一個slot,而是定義了一個slot的數組。這樣在多線程調用exchange的時候,可以各自在不同的slot裡面進行匹配。

exchange的基本思路如下:
(1)根據每個線程的thread id, hash計算出自己所在的slot index;
(2)如果運氣好,這個slot被人佔著(slot裡面有node),並且有人正在等待交換,那就和它進行交換;
(3)slot為空白的(slot裡面沒有node),自己佔著,等人交換。沒人交換,向前挪個位置,把當前slot裡面內容取消,index減半,再看有沒有交換;
(4)挪到0這個位置,還沒有人互動,那就阻塞,一直等著。別的線程,也會一直挪動,直到0這個位置。

所以0這個位置,是一個交易的“終結點”位置。別的位置上找不到人交易,最後都會到0這個位置。

下面是exchange的原始碼:

    public V exchange(V x) throws InterruptedException {        if (!Thread.interrupted()) {            Object v = doExchange(x == null? NULL_ITEM : x, false, 0);            if (v == NULL_ITEM)                return null;            if (v != CANCEL)                return (V)v;            Thread.interrupted(); // Clear interrupt status on IE throw        }        throw new InterruptedException();    }    private Object doExchange(Object item, boolean timed, long nanos) {        Node me = new Node(item);                         int index = hashIndex();                  //根據thread id計算出自己要去的那個交易位置(slot)        int fails = 0;                                    for (;;) {            Object y;                                         Slot slot = arena[index];            if (slot == null)                                    createSlot(index);     //slot = null,建立一個slot,然後會回到for迴圈,再次開始            else if ((y = slot.get()) != null &&  //slot裡面有人等著(有Node),則嘗試和其交換                     slot.compareAndSet(y, null)) { //關鍵點1:slot清空,Node拿出來,倆人在Node裡面互動。把Slot讓給後面的人,做互動地點                Node you = (Node)y;                               if (you.compareAndSet(null, item)) {//把Node裡面的東西,換成自己的                    LockSupport.unpark(you.waiter); //喚醒對方                    return you.item; //自己把對方的東西拿走                } //關鍵點2:如果你運氣不好,在Node裡面要交換的時候,被另一個線程搶了,回到for迴圈,重新開始                                      }            else if (y == null &&                 //slot裡面為空白(沒有Node),則自己把位置佔住                     slot.compareAndSet(null, me)) {                if (index == 0)                   //如果是0這個位置,自己阻塞,等待別人來交換                    return timed? awaitNanos(me, slot, nanos): await(me, slot);                Object v = spinWait(me, slot);    //不是0這個位置,自旋等待                if (v != CANCEL)  //自旋等待的時候,運氣好,有人來交換了,返回                    return v;                me = new Node(item);     //自旋的時候,沒人來交換。走執行下面的,index減半,挪個位置,重新開始for迴圈                    int m = max.get();                if (m > (index >>>= 1))                               max.compareAndSet(m, m - 1);             }            else if (++fails > 1) { //失敗 case1: slot有人,要互動,但被人家搶了  case2: slot沒人,自己要佔位置,又被人家搶了                    int m = max.get();                if (fails > 3 && m < FULL && max.compareAndSet(m, m + 1))                    index = m + 1;   //3次匹配失敗,把index擴大,再次開始for迴圈                             else if (--index < 0)                    index = m;                               }        }    }

注意:上面唯一有點繞的地方,是Slot裡面套了個Node。雙方的交易地點,準確說,不是在Slot裡面,是在Slot的Node裡面。
當Slot的Node = null時候,代表Slot為空白,建立一個Node佔住;
當Slot的Node 。= null的時候,把Node拿出來,雙方在Node裡面交換,此時Slot已經釋放出去了。 總結

Exchanger的代碼雖然少,但還是蠻繞的,總結下來,有以下幾個關鍵點:
(1) Cache Line填充技術
(2) 鎖分離
(3) 倆人還沒交換結束的時候,就把slot給釋放出去了。雙方拿著那個Node,在Node裡面互動的。也就是上面的for迴圈裡面的第2個分支。
這裡面有一種情況就是:運氣不好,2人正要在Node裡面交換的時候,被另外一個線程搶了。那這個沒搶到的,只能for迴圈,重新開始
(4)0是交換的終結點位置:沒有人交換的時候,會不斷挪位置,挪的時候,要把當前位置的slot清空掉,一個人不能同時佔2個Slot。直到挪到0這個位置,一直等待。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.