独占锁:是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。

乐观锁:每次不加锁,假设没有冲突去完成某项操作,如果因为冲突失败就重试,直到成功为止。

一、CAS 操作

乐观锁用到的机制就是CAS,Compare and Swap。

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

1、非阻塞算法 (nonblocking algorithms)

一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。

现代的CPU提供了特殊的指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。

2、AtomicInteger示例

拿出AtomicInteger来研究在没有锁的情况下是如何做到数据正确性的。

  1. private volatile int value;

在没有锁的机制下需要借助volatile原语,保证线程间的数据是可见的(共享的)。

这样才获取变量的值的时候才能直接读取。

  1. public final int get() {
  2. return value;
  3. }

然后来看看 ++i 是怎么做到的。

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

在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。

而compareAndSet利用JNI来完成CPU指令的操作。

  1. public final boolean compareAndSet(int expect, int update) {
  2. return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
  3. }

整体的过程就是这样子的,利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。

而整个J.U.C都是建立在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。参考资料的文章中介绍了如果利用CAS构建非阻塞计数器、队列等数据结构。

二、ABA问题

CAS看起来很爽,但是会导致“ABA问题”。

CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。

比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。因此前面提到的原子操作AtomicStampedReference/AtomicMarkableReference就很有用了。这允许一对变化的元素进行原子操作。

在运用CAS做Lock-Free操作中有一个经典的ABA问题:

线程1准备用CAS将变量的值由A替换为B,在此之前,线程2将变量的值由A替换为C,又由C替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。但实际上这时的现场已经和最初不同了,尽管CAS成功,但可能存在潜藏的问题,例如下面的例子:

现有一个用单向链表实现的堆栈,栈顶为A,这时线程T1已经知道A.next为B,然后希望用CAS将栈顶替换为B:

head.compareAndSet(A,B);

在T1执行上面这条指令之前,线程T2介入,将A、B出栈,再pushD、C、A,此时堆栈结构如下图,而对象B此时处于游离状态:

此时轮到线程T1执行CAS操作,检测发现栈顶仍为A,所以CAS成功,栈顶变为B,但实际上B.next为null,所以此时的情况变为:

其中堆栈中只有B一个元素,C和D组成的链表不再存在于堆栈中,平白无故就把C、D丢掉了。

以上就是由于ABA问题带来的隐患,各种乐观锁的实现中通常都会用版本戳version来对记录或对象标记,避免并发操作带来的问题,在Java中,AtomicStampedReference<E>也实现了这个作用,它通过包装[E,Integer]的元组来对对象标记版本戳stamp,从而避免ABA问题,例如下面的代码分别用AtomicInteger和AtomicStampedReference来对初始值为100的原子整型变量进行更新,AtomicInteger会成功执行CAS操作,而加上版本戳的AtomicStampedReference对于ABA问题会执行CAS失败:

  1. package concur.lock;
  2.  
  3. import java.util.concurrent.TimeUnit;
  4. import java.util.concurrent.atomic.AtomicInteger;
  5. import java.util.concurrent.atomic.AtomicStampedReference;
  6.  
  7. public class ABA {
  8.  
  9. private static AtomicInteger atomicInt = new AtomicInteger(100);
  10. private static AtomicStampedReference<Integer> atomicStampedRef =
  11. new AtomicStampedReference<Integer>(100, 0);
  12.  
  13. public static void main(String[] args) throws InterruptedException {
  14. Thread intT1 = new Thread(new Runnable() {
  15. @Override
  16. public void run() {
  17. atomicInt.compareAndSet(100, 101);
  18. atomicInt.compareAndSet(101, 100);
  19. }
  20. });
  21.  
  22. Thread intT2 = new Thread(new Runnable() {
  23. @Override
  24. public void run() {
  25. try {
  26. TimeUnit.SECONDS.sleep(1);
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. boolean c3 = atomicInt.compareAndSet(100, 101);
  31. System.out.println(c3); //true
  32. }
  33. });
  34.  
  35. intT1.start();
  36. intT2.start();
  37. intT1.join();
  38. intT2.join();
  39.  
  40. Thread refT1 = new Thread(new Runnable() {
  41. @Override
  42. public void run() {
  43. try {
  44. TimeUnit.SECONDS.sleep(1);
  45. } catch (InterruptedException e) {
  46. e.printStackTrace();
  47. }
  48. atomicStampedRef.compareAndSet(100, 101,
  49. atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1);
  50. atomicStampedRef.compareAndSet(101, 100,
  51. atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1);
  52. }
  53. });
  54.  
  55. Thread refT2 = new Thread(new Runnable() {
  56. @Override
  57. public void run() {
  58. int stamp = atomicStampedRef.getStamp();
  59. System.out.println("before sleep : stamp = " + stamp); // stamp = 0
  60. try {
  61. TimeUnit.SECONDS.sleep(2);
  62. } catch (InterruptedException e) {
  63. e.printStackTrace();
  64. }
  65. System.out.println("after sleep : stamp = " + atomicStampedRef.getStamp());//stamp = 1
  66. boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp+1);
  67. System.out.println(c3); //false
  68. }
  69. });
  70.  
  71. refT1.start();
  72. refT2.start();
  73. }
  74.  
  75. }

Reference:

非阻塞算法简介

流行的原子

深入浅出 Java Concurrency (5): 原子操作 part 4

用AtomicStampedReference解决ABA问题

Java CAS 和ABA问题的更多相关文章

  1. 沉淀再出发:java中的CAS和ABA问题整理

    沉淀再出发:java中的CAS和ABA问题整理 一.前言 在多并发程序设计之中,我们不得不面对并发.互斥.竞争.死锁.资源抢占等等问题,归根到底就是读写的问题,有了读写才有了增删改查,才有了所有的一切 ...

  2. java并发编程(十三)----(JUC原子类)引用类型介绍(CAS和ABA的介绍)

    这一节我们将探讨引用类型原子类:AtomicReference, AtomicStampedRerence, AtomicMarkableReference.AtomicReference的使用非常简 ...

  3. Java高性能编程之CAS与ABA及解决方法

    Java高性能编程之CAS与ABA及解决方法 前言 如果喜欢暗色调的界面或者想换换界面,可以看看我在个人博客发布的 Java高性能编程之CAS与ABA及解决方法. CAS概念 CAS,全称Compar ...

  4. Java并发编程入门与高并发面试(三):线程安全性-原子性-CAS(CAS的ABA问题)

    摘要:本文介绍线程的安全性,原子性,java.lang.Number包下的类与CAS操作,synchronized锁,和原子性操作各方法间的对比. 线程安全性 线程安全? 线程安全性? 原子性 Ato ...

  5. JAVA CAS原理深度分析(转)

    看了一堆文章,终于把JAVA CAS的原理深入分析清楚了. 感谢GOOGLE强大的搜索,借此挖苦下百度,依靠百度什么都学习不到! 参考文档: http://www.blogjava.net/xylz/ ...

  6. Java CAS 原理分析

    1.简介 CAS 全称是 compare and swap,是一种用于在多线程环境下实现同步功能的机制(可以把 CAS 看做乐观锁).CAS 操作包含三个操作数 -- 内存位置.预期数值和新值.CAS ...

  7. 转:JAVA CAS原理深度分析

    看了一堆文章,终于把Java CAS的原理深入分析清楚了. 感谢GOOGLE强大的搜索,借此挖苦下百度,依靠百度什么都学习不到! 参考文档: http://www.blogjava.net/xylz/ ...

  8. Java CAS总结

    文章目录 1. CPU指令对CAS的支持(CPU的cas指令是原子的) 或许我们可能会有这样的疑问,假设存在多个线程执行CAS操作并且CAS的步骤很多,有没有可能在判断V和E相同后,正要赋值时,切换了 ...

  9. CAS及其ABA问题

    CAS.volatile是JUC包实现同步的基础.Synchronized下的偏向锁.轻量级锁的获取.释放,lock机制下锁的获取.释放,获取失败后线程的入队等操作都是CAS操作锁标志位.state. ...

随机推荐

  1. python学习笔记3-celery分布式任务处理器

    celery是用python写的一个异步的任务框架,功能非常强大,具体的说明可以查看官网,这里主要提供点demo让你迅速使用该框架   1.环境安装 默认安装好了redis pip install c ...

  2. Appium学习路-打包apk和ipa篇

    间隔这么长时间再去写Appium的学习篇是有原因的,因为在想要用appium测试ios时,发现appium只能测试debug版本的ipa包.然后就需要自己去学习打包了啊.然后就对xcode各种不了解, ...

  3. 利用Maven建立java web项目

    方法一:在IntelliJ IDEA中创建maven web项目 一.在新建项目对话框中,选择“Maven”类型,设置好jdk,勾选“create from archetype”,在列表中选择“mav ...

  4. 点亮一个led灯

    /********************************* 代码功能:点亮一个led灯 使用函数: pinMode(引脚号,模式); digitalWrite(引脚号,电平状态); //默认 ...

  5. iOS 拨打电话三种方法

    小弟查了很多地方的关于iOS程序拨打电话,大都不全,今天我总结了三种方法,各有不同,拿来给大家分享,希望给大家有所帮助1,这种方法,拨打完电话回不到原来的应用,会停留在通讯录里,而且是直接拨打,不弹出 ...

  6. jQuery选择器和DOM操作——《锋利的jQuery》(第2版)读书笔记1

    第1章 认识jQuery jQuery有以下优势: 轻量级: 强大的选择器: 出色的DOM操作的封装: 可靠的事件处理机制: 完善的Ajax: 不污染顶级变量: 出色的浏览器兼容性: 链式操作方式: ...

  7. 理解AOP

    http://www.cnblogs.com/yanbincn/archive/2012/06/01/2530377.html Aspect Oriented Programming  面向切面编程. ...

  8. jquery星级评论打分组件

    <!DOCTYPE HTML><html> <head> <meta charset="utf-8"><title>jq ...

  9. Dedecms 首页调用副栏目内容方法

    最近一段时间一直打算修改英国移民网的首页,对首页的栏目内容也打算进行一系列的调用设置,可是在调用的时候出现了一些问题:调用不了英国移民网下的“移民快讯”这一副栏目下的内容!于是只能上网求助,在广大的网 ...

  10. iOS 微信支付

    相关资料 SDK下载:https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=11_1 APP端开发步骤说明:https://pay.weixin ...