鏈表的無鎖操作 (JAVA)
看了下網上關於鏈表的無鎖操作,寫的不清楚,遂自己整理一部分,主要使用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)