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

 更新时间:2017年09月14日 08:50:05   投稿:mengwei  
这篇文章主要介绍了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指令的无锁编程实现实例的全部介绍,希望对大家有所帮助!

相关文章

  • java实现微信H5支付方法详解

    java实现微信H5支付方法详解

    本篇文章主要介绍了java实现微信H5支付方法详解,非常具有实用价值,需要的朋友可以参考下
    2017-04-04
  • java实现手写一个简单版的线程池

    java实现手写一个简单版的线程池

    有些人可能对线程池比较陌生,并且更不熟悉线程池的工作原理。本文就来手写一个简单版的线程池,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • IntelliJ IDEA打开多个Maven的module且相互调用代码的方法

    IntelliJ IDEA打开多个Maven的module且相互调用代码的方法

    这篇文章主要介绍了IntelliJ IDEA打开多个Maven的module且相互调用代码的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-02-02
  • Spring Boot 2.x基础教程之配置元数据的应用

    Spring Boot 2.x基础教程之配置元数据的应用

    这篇文章主要介绍了Spring Boot 2.x基础教程之配置元数据的应用,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-01-01
  • SpringBoot整合Xxl-job实现定时任务的全过程

    SpringBoot整合Xxl-job实现定时任务的全过程

    XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展,下面这篇文章主要给大家介绍了关于SpringBoot整合Xxl-job实现定时任务的相关资料,需要的朋友可以参考下
    2022-01-01
  • Logback 使用TurboFilter实现日志级别等内容的动态修改操作

    Logback 使用TurboFilter实现日志级别等内容的动态修改操作

    这篇文章主要介绍了Logback 使用TurboFilter实现日志级别等内容的动态修改操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • 基于list stream: reduce的使用实例

    基于list stream: reduce的使用实例

    这篇文章主要介绍了list stream: reduce的使用实例,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • Mybatis调用视图和存储过程的方法

    Mybatis调用视图和存储过程的方法

    MyBatis支持普通sql的查询、视图的查询、存储过程调用,是一种非常优秀的持久层框架。接下来通过本文给大家介绍mybatis调用视图和存储过程的方法,感兴趣的朋友一起看看吧
    2016-10-10
  • java实现扑克牌牌面小程序

    java实现扑克牌牌面小程序

    这篇文章主要为大家详细介绍了java实现扑克牌牌面小程序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-11-11
  • java实现连接mysql数据库单元测试查询数据的实例代码

    java实现连接mysql数据库单元测试查询数据的实例代码

    下面小编就为大家带来一篇java实现连接mysql数据库单元测试查询数据的实例代码。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-10-10

最新评论