基于CAS实现的组件有很多,这里选择非阻塞同步队列的原因是,当有多个线程同时添加元素时,在队列的尾部如何利用cas保证两个操作的原子性是有相应算法的(这两个步骤是尾节点需要指向新增的元素,最后一个节点元素需要指向新增的元素,即新增的元素必须同时有这两个节点指向它)。代码如下:
public class ConcurentQueue {
static class Node {
public Object e;
public AtomicReference nextRef ;
public Node(Object e,Node next) {
super();
this.e = e;
this.nextRef = new AtomicReference(next);
}
}
private Node head = new Node(null,null);
private Node tail = new Node(null,head);
public void add(Object e) {
Node new_node = new Node(e,null);//待插入节点
while(true){
Node curLast = tail.nextRef.get();//当前最后节点
Node next = curLast.nextRef.get();//当前最后节点的next节点
if(next != null){//A 如果当前最后节点的next节点不为空,说明尾节点需要指向next节点
tail.nextRef.compareAndSet(curLast, next);
}else{
if(curLast.nextRef.compareAndSet(null, new_node)){//* B 将当前最后节点的next节点设置为新插入的节点
tail.nextRef.compareAndSet(curLast, new_node);//* C 将尾节点的next节点设置为新插入的节点
return;
}
}
}
}
public Object remove() {
while (true) {
Node next = head.nextRef.get();
if (next == null) {
return null;
} else {
Node nextSetNode = next.nextRef.get();
if (head.nextRef.compareAndSet(next, nextSetNode)) {
return next.e;
}
}
}
}
}
打*的两个地方原本是需要同时成功才能保证正确性的,但根据现在了解的是cas只能保证一个操作的原子性,若要保证两个操作的原子性,初看是不可能的。细心的朋友可以看出这些代码和java并发编程实战中的是几乎一样的,也只有将head节点也作为哑节点这一小小改动,(注意尾节点和最后一个节点是不同的节点),首先队列永远只有两种状态,见书中的原图(不过描述得改成中间态和稳定态,其余的不要看尤其是那个”对立“让人看不懂):
其次队列永远只能在最后一个节点后面即稳定态的时候才能插入!
先分析一下代码:
1、当B步骤成功后,还没来得及执行C步骤前,此时队列就处于中间态,其余线程就会执行A步骤将尾节点推到最后一个节点上从而形成稳定态,此时再执行C步骤时尽管失败了但是队列已经稳定;
2、只要队列不稳定时,所有的线程都会设法执行A步骤将队列趋于稳定;
3、C步骤不管是成功还是失败队列都会处于稳定状态;
到这里大家可以去体会一下java并发编程实战中说的:那两个技巧了,说实在的没看懂代码前能看懂那些话么....,还有能想到这种方法确实不容易,想想还是用锁实现比较简单明了。