这篇文章主要介绍了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指令了吧。。
好啦,那么接下来来看看在java中cas都用在了什么地方了吧,首先来看atomicinteger类型吧,这个是并发库里面提供的一个类型:
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类型的重要方法都是采用无锁的方式实现的。。因此在并发环境下,用这种类型能有更好的性能。。。
上面算是搞定了在java中实现无锁的计数器,接下来来看看如何实现无锁栈,直接贴代码了,代码是从《java并发编程实战》中模仿下来的:
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;
}
}
}
好啦,上面的代码就算是实现了一个无锁的栈,简单吧。。。在并发环境中,无锁的数据结构伸缩性能够比用锁好得多。。。
在提到无锁编程的时候,就不得不提到无锁队列,其实在concurrent库中已经提供了无锁队列的实现:concurrentlinkedqueue,我们来看看它的重要的方法实现吧:
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指令的无锁编程的实例的详细内容。