看了下网上关于链表的无锁操作,写的不清楚,遂自己整理一部分,主要使用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(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. 链表某节点插入
实现原理相同,不多赘述,一定要注意和删除的判断过程是保证自洽的。