Java乐观锁实现之CAS操作

2018-08-26 17:18:45来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

介绍CAS操作前,我们先简单看一下乐观锁 与 悲观锁这两个常见的锁概念。

悲观锁:

  从Java多线程角度,存在着“可见性、原子性、有序性”三个问题,悲观锁就是假设在实际情况中存在着多线程对同一共享的竞争,所以在操作前先占有共享资源(悲观态度)。因此,悲观锁是阻塞,独占的,存在着频繁的线程上下文切换,对资源消耗较大。synchronized就是悲观锁的一种实现。

乐观锁:

  如名一样,每次操作都认为不会发生冲突,尝试执行,并检测结果是否正确。如果正确则执行成功,否则说明发生了冲突,回退再重新尝试。乐观锁的过程可以分为两步:冲突检测 和 数据更新。在Java多线程中乐观锁一个常见实现即:CAS操作。

 

CAS

  CAS,(Compare-And-Swap,比较和替换)。其具有三个操作数:内存地址V,旧的预期值A,新的预期值B。当V中的值和A相同时,则用新值B替换V中的值,否则不执行更新。(PS:上述的操作是原子性的,因为过程是:要么执行更新,要么不更新)

  在JDK1.5新增的java.util.concurrent(J.U.C) 就是建立在CAS操作上的。CAS是一种非阻塞的实现(PS:乐观锁采用一种 “自旋锁”的技术,其原理是:如果存在竞争,则没有获得资源的线程不立即挂起,而是采用让线程执行一个忙循环(自旋)的方式,等待一段时间看是否能获得锁,如果超出规定时间再挂起),所以J.U.C在性能上有很大的提升。下面以J.U.C下的AtomicInteger的部分源码为例,看一下CAS的过程究竟如何。

public class AtomicInteger extends Number implements java.io.Serializable {
    private volatile int value;

    public final int get() {
        return value;
    }

    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
  // CAS操作
    public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }

}

  以上截取自AtomicInteger的部分源码。CAS操作的核心就在 getAndIncrement()方法中,在此方法调用的compareAndSet(int expect , int update) 的两个参数可知,当current值符合expect时,用next替换了current。如果不符合,则在for中不断尝试知道成功为止。在这里的步骤,就相当于一个原子性的 ++i 了。(PS: 单纯的 ++i 不具备原子性)

  其中,compareAndSet方法的实现得益于硬件的发展:多条步骤的操作行为可以通过一条指令完成。(在此是利用JNI来完成CPU指令的操作)

 

 

CAS存在的问题

1. ABA问题

 ABA问题值,内存地址V的值是A,线程one从V中取出了A,此时线程two也从V中取出了A,同时将A修改为B,但是又因为一些原因修改为A。而此时线程one仍看到V中的值为A,认为没有发生变化,此为ABA问题。解决ABA问题一种方式是通过版本号(version)。每次执行数据修改时,都需要带上版本号,如:1A,2B,3A。通过比较版本号可知是否有发生过操作,也就解决了ABA问题。  

2. 未知的等待时长

  因为CAS采取失败重试的策略,所以不确定会发生多少次循环重试。如果在竞争激烈的环境下,其重试次数可能大幅增加。此时效率也就降低了。

 

总结

  乐观锁、悲观锁是一种思想,CAS是乐观锁的一种实现。前者是非阻塞同步,非独占,而后者是阻塞同步,独占锁。在可预知的情况下,如果竞争冲突发生较少,乐观锁是个不错的选择。而如果竞争激烈,悲观锁应得到考虑。

  关于对象的创建分配内存,因为多线程分配对象空间并不安全,如分配A,B两对象,当给A分配内存时,指针还没修改,就切换到给B分配同一块内存,引发错误。其中一种解决方法就是通过底层的CAS操作来保证分配的原子性。

 

 

如有错误,敬请斧正,以防误导他人。

参考:http://www.importnew.com/20472.html

  

 

 

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:0jvm作用

下一篇:java 爬取最近四川招标信息