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

作者:mengwei 时间:2022-10-13 19:20:28 

最开始接触到相关的内容应该是从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指令的无锁编程实现实例的全部介绍,希望对大家有所帮助!

来源:https://www.2cto.com/kf/201312/261150.html

标签:java,cas,无锁算法
0
投稿

猜你喜欢

  • JAVA设置手动提交事务,回滚事务,提交事务的操作

    2022-07-20 08:07:40
  • 基于使用BeginInvoke,EndInvoke异步调用委托的实现代码

    2023-04-29 09:46:49
  • C#实现文件上传与下载功能实例

    2022-11-18 07:59:03
  • java单向链表的实现实例

    2023-10-23 20:36:10
  • Android 判断某个Activity 是否在前台运行的实例

    2023-07-24 19:29:21
  • Spring boot 整合Logback过程示例解析

    2021-12-06 04:05:52
  • 详解Struts2动态方法调用

    2022-10-18 11:19:25
  • SpringMVC中重定向model值的获取方式

    2022-08-25 11:22:09
  • C# using()的使用方法

    2022-03-02 23:23:37
  • 深入了解Java线程池的原理使用及性能优化

    2023-02-17 22:35:31
  • Java使用pulsar-flink-connector读取pulsar catalog元数据代码剖析

    2023-11-05 17:25:41
  • java从输入流中获取数据并返回字节数组示例

    2021-12-08 22:47:36
  • Spring 4.0新功能:@Conditional注解详细介绍

    2022-01-19 06:37:35
  • Unity调用手机摄像机识别二维码

    2023-05-18 23:56:36
  • 探讨如何用委托处理排序

    2023-12-17 15:06:36
  • java集合collection接口与子接口及实现类

    2021-07-02 10:59:04
  • IDEA 2020.3.X 创建scala环境的详细教程

    2022-07-16 11:44:47
  • Java synchronized轻量级锁实现过程浅析

    2022-05-08 07:28:55
  • Android Bluetooth蓝牙技术使用流程详解

    2022-07-07 02:41:16
  • java 启动exe程序,传递参数和获取参数操作

    2023-09-11 04:30:47
  • asp之家 软件编程 m.aspxhome.com