欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java乐观锁实现之CAS操作

程序员文章站 2022-03-17 09:36:55
介绍CAS操作前,我们先简单看一下乐观锁 与 悲观锁这两个常见的锁概念。 悲观锁: 从Java多线程角度,存在着“可见性、原子性、有序性”三个问题,悲观锁就是假设在实际情况中存在着多线程对同一共享的竞争,所以在操作前先占有共享资源(悲观态度)。因此,悲观锁是阻塞,独占的,存在着频繁的线程上下文切换, ......

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