Java语言中cas指令的无锁编程实现实例
最开始接触到相关的内容应该是从volatile关键字开始的吧,知道它可以保证变量的可见性,而且利用它可以实现读与写的原子操作。。。但是要实现一些复合的操作volatile就无能为力了。。。最典型的代表是递增和递减的操作。。。。 我们知道,在并发的环境下,要实现数据的一致性,最简单的方式就是加锁,保证同一时刻只有一个线程可以对数据进行操作。。。。例如一个计数器,我们可以用如下的方式来实现: public class Counter { private volatile int a = 0; public synchronized int incrAndGet(int number) { this.a += number; return a; } public synchronized int get() { return a; } } 我们对操作都用synchronized关键字进行修饰,保证对属性a的同步访问。。。这样子确实可以保证在并发环境下a的一致性,但是由于使用了锁,锁的开销,线程的调度等等会使得程序的伸缩性受到了限制,于是就有了很多无锁的实现方式。。。。 其实这些无锁的方法都利用了处理器所提供的一些CAS(compare and switch)指令,这个CAS到底干了啥事情呢,可以用下面这个方法来说明CAS所代表的语义: public synchronized int compareAndSwap(int expect,int newValue) { int old = this.a; if (old == expect) { this.a = newValue; } return old; } 好吧,通过代码应该对CAS语义的标书很清楚了吧,好像现在大多数的处理器都实现了原子的CAS指令了吧。。 private volatile int value; 这个是内部定义的一个属性吧,用于保存值,由于是volatile类型的,所以可以保证线程之间的可见性以及读写的原子性。。。 public final int addAndGet(int delta) { for (;;) { int current = get(); int next = current + delta; if (compareAndSet(current,next)) return next; } } 这个方法的作用是在当前值的基础上加上delta,这里可以看到整个方法中并没有加锁,这代码其实就算是java中实现无锁计数器的方法,这里compareAndSet方法的定义如下: public final boolean compareAndSet(int expect,int update) { return unsafe.compareAndSwapInt(this,valueOffset,expect,update); } 由于调用了unsafe的方法,所以这个就无能为力了,其实应该能猜到JVM调用了处理器本身的CAS指令来实现原子的操作。。。 基本上AtomicInteger类型的重要方法都是采用无锁的方式实现的。。因此在并发环境下,用这种类型能有更好的性能。。。 package concurrenttest; import java.util.concurrent.atomic.AtomicReference; public class ConcurrentStack<e> { AtomicReference<node<e>> top = new AtomicReference<node<e>>(); public void push(E item) { Node<e> newHead = new Node<e>(item); Node<e> oldHead; while (true) { oldHead = top.get(); newHead.next = oldHead; if (top.compareAndSet(oldHead,newHead)) { return; } } } public E pop() { while (true) { Node<e> oldHead = top.get(); if (oldHead == null) { return null; } Node<e> newHead = oldHead.next; if (top.compareAndSet(oldHead,newHead)) { return oldHead.item; } } } private static class Node<e> { public final E item; public Node<e> next; public Node(E item) { this.item = item; } } } 好啦,上面的代码就算是实现了一个无锁的栈,简单吧。。。在并发环境中,无锁的数据结构伸缩性能够比用锁好得多。。。 public boolean offer(E e) { checkNotNull(e); final Node<e> newNode = new Node<e>(e); for (Node<e> t = tail,p = t;;) { Node<e> q = p.next; if (q == null) { // p is last node if (p.casNext(null,newNode)) { // Successful CAS is the linearization point // for e to become an element of this queue,// and for newNode to become "live". if (p != t) // hop two nodes at a time casTail(t,newNode); // Failure is OK. return true; } // Lost CAS race to another thread; re-read next } else if (p == q) // We have fallen off list. If tail is unchanged,it // will also be off-list,in which case we need to // jump to head,from which all live nodes are always // reachable. Else the new tail is a better bet. p = (t != (t = tail)) ? t : head; else // Check for tail updates after two hops. p = (p != t && t != (t = tail)) ? t : q; } } 这个方法用于在队列的尾部添加元素,这里可以看到没有加锁,对于具体的无锁算法,采用的是Michael-Scott提出的非阻塞链表链接算法。。。具体是怎么样子的,可以到《JAVA并发编程实战》中去看吧,有比较详细的介绍。 另外对于其他方法,其实都是采用无锁的方式实现的。 最后,在实际的编程中,在并发环境中最好还是采用这些无锁的实现,毕竟它的伸缩性更好。 总结 以上是本文关于Java语言中cas指令的无锁编程实现实例的全部介绍,希望对大家有所帮助! (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |