1. 程式人生 > >鏈表的無鎖操作 (JAVA)

鏈表的無鎖操作 (JAVA)

als ucc 節點刪除 發包 完成 continue 刪除節點 icm ble

看了下網上關於鏈表的無鎖操作,寫的不清楚,遂自己整理一部分,主要使用concurrent並發包的CAS操作。

1. 鏈表尾部插入

待插入的節點為:cur

尾節點:pred

基本插入方法:

do{

  pred = find_tail();              //重新找尾節點

}(! pred.next.compareAndSet(NULL, cur))  //pred.next 是否為NULL,是則將其指向cur,不是則有新的節點插入

這種插入方法是不帶標記的,如果不涉及鏈表刪除這個方法是可行的。

但是如果有刪除操作,pred節點可能會被刪除,所以我們需要再加入一個標記位判斷pred是否被刪除(false 未刪除)

do{

  pred = find_tail();              

}(! pred.next.compareAndSet(NULL, cur, false, false));  //檢查pred.next是被否標記為false,並標記為false

因為我們插入是對pred.next進行CAS操作,所以雖然要判斷pred是否被刪除,我們卻只能看它的next域的標記,所以在刪除的時候要和這裏配合置next位為true讓本次添加不成功

註意這裏的next的置位與否不是next節點的,是pred本身的,因為只能從pred自己看到是否被刪除,所以節點的數據結構不包含flag,節點的next域包含flag,這種結構具體參考AtomicMarkableReference

這裏如果先判斷pred是否為false是不行的,所有操作必須在CAS中完成,因為判斷的一瞬間是OK的但是不代表執行CAS的時候也是OK的

(TIP:因為是隊尾,next域是NULL,但是仍然含有標記位,每個新node尾節點添加的時候都是node.next = new AtomicMarkableReference<Node>(null,false))

2. 鏈表刪除節點

待刪除的節點為:cur

do{

  succ = cur.next;        //重新找next節點

  if (cur.next == true) break;   //有線程已經將其邏輯刪除

  pred = pred(cur);       //找pred節點

  flag1 = cur.next.compareAndSet(succ, succ, false, true);  //第一個CAS將cur.next置為true,防止添加操作和重復刪除操作

  if (! flag1) continue;

  flag2 = pred.next.compareAndSet(cur, succ, false, false);  //第二個CAS將pred.next指向succ,並將succ的為置為false

  if(! flag2) continue;

}(0);

這裏兩次CAS操作,第一次置位,也就是所謂的邏輯刪除,和添加節點相呼應,這裏標記完了之後在第二個CAS執行前,往本節點之後的添加都不成功

本次CAS失敗說明有其它線程的刪除操作或CAS之前有往cur的next有添加操作(succ改變),返回再次嘗試

第二次則將原來指向cur的pred.next指向succ,進行物理刪除,這裏succ已經不能改變了,因為第一次CAS成功之後next置位其他線程既不能添加也不能刪除

但是pred可以被刪除,pred被刪除之後導致pred.next置位,這次CAS失敗。

這裏第二次CAS操作可能其他地方也有相同的代碼,如果本線程在第一次CAS調用完後阻塞,可以讓其他線程看到這個標記後幫忙進行節點刪除和清置位,這也是wait free的設計的核心思想

3. 鏈表某節點插入

實現原理相同,不多贅述,一定要註意和刪除的判斷過程是保證自洽的。

鏈表的無鎖操作 (JAVA)