Java语言中cas指令的无锁编程实现实例

网友投稿 238 2023-04-04


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所代表的语义:

puhttp://blic 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 {

AtomicReference> top = new AtomicReference>();

public void push(E item) {

Node newHead = new Node(item);

Node oldHead;

while (true) {

oldHead = top.get();

newHead.next = oldHead;

if (top.compareAndSet(oldHead, newHead)) {

return;

}

}

}

public E pop() {

while (true) {

Node oldHead = top.get();

if (oldHead == null) {

return null;

}

Node newHead = oldHead.next;

if (top.compareAndSet(oldHead, newHead)) {

return oldHead.item;

}

}

}

private static class Node {

public final E item;

public Node next;

public Node(E item) {

this.item = item;

}

}

}

好啦,上面的代码就算是实现了一个无锁的栈,简单吧。。。在并发环境中,无锁的数据结构伸缩性能够比用锁好得多。。。

在提到WuiJYrgDV无锁编程的时候,就不得不提到无锁队列,其实在concurrent库中已经提供了无锁队列的实现:ConcurrentLinkedQueue,我们来看看它的重要的方法实现吧:

public boolean offer(E e) {

checkNotNull(e);

final Node newNode = new Node(e);

for (Node t = tail, p = t;;) {

Node 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指令的无锁编程实现实例的全部介绍,希望对大家有所帮助!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:使用Fullpage插件快速开发整屏翻页的页面
下一篇:编写接口需求文档测试用例(如何编写接口测试用例)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~