Java多線程 -- JUC包源碼分析17 -- 弱一致性與無鎖隊列__無鎖隊列

來源:互聯網
上載者:User

ConcurrentHashMap的弱一致性
SynchronousQueue的弱一致性
Exchanger的弱一致性
Linux核心無鎖隊列的弱一致性
總結

經過前面一系列的源碼分析,我們基本覆蓋了JUC包的所有組件。在這諸多組件中,我們總是不斷看到一個如影隨行的東西:CAS。

相當鎖來講,它的原子粒度更小,只是作用在一個基本變數上面(比如一個Integer, Long, 或者Reference),而不像Lock那樣,全域加鎖,因此它的並發度更大。

但任何事情總是有2面,在帶來高並發度的同時,也帶來了另一個問題:“弱一致性”。
因為“弱一致性”的存在,極大的增加了我們的編碼難度。在前面ConcurrentHashMap,Exchanger, SynchronousQueue的分析中,我們都在一定程度上,感受都了“弱一致性”所帶來的編碼複雜度。

本文試圖對前面所講到的諸多“弱一致性問題“進行一個全面梳理,同時分析一下Linux核心的一個“地道的“真正的無鎖隊列,以及它所面對的弱一致性問題。

希望最終可以讓大家對”弱一致性“有一個深刻理解,這也就能更好的理解,為什麼”弱一致性“給編碼帶來了諸多複雜性。 ConcurrentHashMap的弱一致性 case1: clear函數的弱一致性

clear執行完畢,map中仍然還有元素存在。
原因: 因為是每個segment分別加鎖,clear下一個segment的時候,上一個segment的鎖已經釋放,此時其他線程可以再往裡面放元素。 case2: put進去的元素,get出來為null

原因:因為tab[index] = new HashEntry case 3: put進去的元素,get出來為空白

原因:因為get不加鎖,在put執行 count = c 那行代碼之前,雖然元素放進去了,但因為沒有happen before約束,可能get不到。(具體代碼此處就不再次列出,參見前面ConcurrentHashMap源碼分析) SynchronousQueue的弱一致性

在前面我們知道,TransferQueue/TransferStak都是基於一個單向鏈表實現的。多線程訪問這個鏈表的時候,並沒有加鎖,只是對head/tail進行了CAS訪問。因此就造成了以下的”弱一致性“問題:

//TransferQueue.transfer        Object transfer(Object e, boolean timed, long nanos) {            QNode s = null; // constructed/reused as needed            boolean isData = (e != null);            for (;;) {                QNode t = tail;                QNode h = head;                if (t == null || h == null)                            continue;                                    if (h == t || t.isData == isData) {                     QNode tn = t.next;                    if (t != tail)            //上面明明賦值的t = tail,此處卻出現了t != tail。這是因為有其他線程在對tail做CAS操作。因此造成inconsist read                        continue;          。。。}
Exchanger的弱一致性
 private Object doExchange(Object item, boolean timed, long nanos) {        Node me = new Node(item);                         int index = hashIndex();           int fails = 0;                                    for (;;) {            Object y;                                         Slot slot = arena[index];            if (slot == null)                                    createSlot(index);                 else if ((y = slot.get()) != null &&                       slot.compareAndSet(y, null)) { //slot裡面有人等待交換,則把slot清空,Node拿出來,倆人在Node裡面互動。把Slot讓給後面的人,做互動地點                Node you = (Node)y;                               if (you.compareAndSet(null, item)) {//slot清空了,正要準備在Node裡面交換呢,卻被別的線程搶了,導致you.compareAndSet失敗。可謂螳螂撲蟬,黃雀在後                    LockSupport.unpark(you.waiter);                     return you.item;                 }                                      }    。。。 }

上述問題之所以會發生,就是因為slot.comareAndSet和you.compareAndSet,各自分別都是原子的,但合在一起,卻不是原子的。 Linux核心無鎖隊列的弱一致性

在Linux核心中,有一個基於RingBuffer做的完全無鎖的隊列kfifo,連CAS都沒有用。當然,它有個前提:只能1讀1寫。
源碼連結如下:
https://github.com/opennetworklinux/linux-3.8.13/blob/master/kernel/kfifo.c

struct kfifo {       unsigned char *buffer;    /* the buffer holding the data */       unsigned int size;    /* the size of the allocated buffer */       unsigned int in;    /* data is added at offset (in % size) */       unsigned int out;    /* data is extracted from off. (out % size) */       spinlock_t *lock;    /* protects concurrent modifications */   };

入隊的時候,操作變數in;出隊的時候,操作變數out。

unsigned int __kfifo_in(struct __kfifo *fifo,        const void *buf, unsigned int len){    unsigned int l;    l = kfifo_unused(fifo);    if (len > l)        len = l;    kfifo_copy_in(fifo, buf, len, fifo->in);    fifo->in += len;   //入隊,in指標前移    return len;}unsigned int __kfifo_out(struct __kfifo *fifo,        void *buf, unsigned int len){    len = __kfifo_out_peek(fifo, buf, len);    fifo->out += len;  //出對, out指標前移    return len;}static inline unsigned int kfifo_unused(struct __kfifo *fifo){    return (fifo->mask + 1) - (fifo->in - fifo->out);  //判斷未用的空間}unsigned int __kfifo_out_peek(struct __kfifo *fifo,        void *buf, unsigned int len){    unsigned int l;    l = fifo->in - fifo->out;    if (len > l)        len = l;    kfifo_copy_out(fifo, buf, len, fifo->out);    return len;}

從上面可以看出,無論是int, out,還是判斷隊列是否為空白/為滿,都沒有加鎖,也沒有CAS,至所以能做到這點,是因為2個前提:
(1)只有1讀1寫,一個操作in,一個操作out
(2)弱一致性:放的時候,隊列沒有滿,可能會判斷成已滿;取的時候,隊列不為空白,但判斷成已空。但沒有關係,生產者/消費者本來就是迴圈調用的,本次取不到,迴圈回來重試的時候,可能就取到了。

當然,關於kfifo,還有其他一些小技巧在裡面,此次不再詳述。大家可以參加下面的文章
http://blog.csdn.net/linyt/article/details/5764312 總結

在上面,我們列舉了諸多“弱一致性”的例子,總結下來:
(1)至所以會出現這個問題,就是因為不加鎖,或者鎖的粒度太細(CAS)。沒辦法像Lock那樣,可以有臨界區,”大面積“的加鎖,實現多個操作合在一起的原子操作。
(2)這個問題的解決辦法,通常就是”迴圈重試“。因為inconsistent read,那就再讀一次。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.