当前位置: 移动技术网 > IT编程>开发语言>Java > Java乐观锁实现之CAS操作

Java乐观锁实现之CAS操作

2018年08月23日  | 移动技术网IT编程  | 我要评论

介绍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

  

 

 

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网