分享

Java并发:CAS、ABA问题、ABA问题解决方案

 liang1234_ 2019-01-30

【1】锁

1、加锁的机制
参见:java线程安全和锁机制详解
网址:http://smallbug-vip./blog/2275743

2、锁的机制有如下问题
(1)在多线程环境下,加锁、释放锁会导致比较多的上下文切换和调度延时,从而引起性能问题。
(2)一个线程持有锁会导致其他所有需要此锁的线程挂起。
(3)如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。

3、悲观锁和乐观锁
(1)独占锁:是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
乐观锁:每次不加锁,假设没有冲突去完成某项操作,如果因为冲突失败就重试,直到成功为止。
(2)volatile是不错的机制,但是volatile不能保证原子性。因此,对于同步问题最终还是要回到锁机制上来。
【优质文章】深入理解Java内存模型(四)——volatile:http://www./lib/view/open1459412319988.html

【2】CAS方法:CompareAndSwap

1、乐观锁的使用的机制就是CAS。
在CAS方法中,CAS有三个操作数,内存值V,旧的预期值E,要修改的新值U。当且仅当预期值E和内存值V相等时,将内存值V修改为U,否则什么都不做。

2、非阻塞算法(nonblocking algorithms):一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。
(1)非阻塞算法简介:https://www.ibm.com/developerworks/cn/java/j-jtp04186/
(2)非阻塞算法通常叫作乐观算法,因为它们继续操作的假设是不会有干扰。如果发现干扰,就会回退并重试。

3、CAS方法
(1)CompareAndSwap()就使用了非阻塞算法来代替锁定。
(2)举例:AtomicInteger来研究在没有锁的情况下是如何做到数据正确性的。
在没有锁机制的情况下,要保证线程间的数据是可见的,就会常常用到volatile原语了。
private volatile int value;

可使用如下方法读取内存变量值value:

public final int getValue(){
    return value;
}
  • 1
  • 2
  • 3

递增计数器是如何实现的:

public final int incrementAndGet(){
    for(;;){
        int current = getValue();
        int next = value + 1;
        if(compareAndSet(current,next)){
            return next;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

该方法采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。
而compareAndSet操作利用了Java本地接口(JNI,Java Native Interface)完成CPU指令的操作:

public final boolean compareAndSet(int expect, int update){
    return unsafe.compareAndSwapInt(this,valueOffset,expect,unsafe);
}
  • 1
  • 2
  • 3
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
      try {
        valueOffset = unsafe.objectFieldOffset(*.class.getDeclaredField("value"));
      } catch (Exception ex) { 
      throw new Error(ex); }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

【3】“ABA”问题

1、可以发现,CAS实现的过程是先取出内存中某时刻的数据,在下一时刻比较并替换,那么在这个时间差会导致数据的变化,此时就会导致出现“ABA”问题。
2、什么是”ABA”问题?
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。
尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。

【4】用AtomicStampedReference/AtomicMarkableReference解决ABA问题

1、原子操作:http://www./xylz/archive/2010/07/02/325079.html
2、用AtomicStampedReference解决ABA问题:http://blog./2011/09/resolve-aba-by-atomicstampedreference.html
3、高并发Java(4):无锁: http://www./21282.html
4、AtomicStampedReference、AtomicMarkableReference源码分析,解决cas ABA问题: https://blog.csdn.net/zqz_zqz/article/details/68062568
5、关于AtomicStampedReference使用的坑: https://blog.csdn.net/xybz1993/article/details/79992120
6、JAVA中的CAS: https://blog.csdn.net/mmoren/article/details/79185862
7、看看别人的Java面试 你是否又有学习的动力了?: http://www./software/707.html
注:以上内容均来自网络,该内容仅作为学习参考

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多