什么是CAS机制

CAS是英文单词Compare and Swap的缩写,翻译过来就是比较并替换

CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。

看如下几个例子:

package com.example.demo.concurrentDemo;

import org.junit.Test;

import java.util.concurrent.atomic.AtomicInteger;

public class CasTest {

    private static int count = 0;

    @Test
public void test1(){
for (int j = 0; j < 2; j++) {
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
count++;
}
}).start();
} try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} //结果必定 count <= 20000
System.out.println(count);
} @Test
public void test2() {
for (int j = 0; j < 2; j++) {
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
synchronized (this) {
count++;
}
}
}).start();
} try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//synchronized 类似于悲观锁
//synchronized关键字会让没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态
//这个过程中涉及到操作系统用户模式和内核模式的转换,代价比较高
System.out.println(count);
} private static AtomicInteger atoCount = new AtomicInteger(0); @Test
public void test3() {
for (int j = 0; j < 2; j++) {
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
atoCount.incrementAndGet();
}
}).start();
} try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} //Atomic操作类的底层正是用到了“CAS机制”
System.out.println(atoCount);
} }

CAS 缺点

1) CPU开销过大

在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很到的压力。

这个可以通过看:AtomicInteger.incrementAndGet()源码,可知这是一个无限循环,获取实际值与预期值比较,当相等才会跳出循坏。

2) 不能保证代码块的原子性

CAS机制所保证的知识一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。

3) ABA问题

这是CAS机制最大的问题所在。

什么是ABA?先看下面例子:

我们先来看一个多线程的运行场景:
时间点1 :线程1查询值是否为A 
时间点2 :线程2查询值是否为A 
时间点3 :线程2比较并更新值为B 
时间点4 :线程2查询值是否为B 
时间点5 :线程2比较并更新值为A 
时间点6 :线程1比较并更新值为C

在这个线程执行场景中,2个线程交替执行。线程1在时间点6的时候依然能够正常的进行CAS操作,尽管在时间点2到时间点6期间已经发生一些意想不到的变化, 但是线程1对这些变化却一无所知,因为对线程1来说A的确还在。通常将这类现象称为ABA问题。
ABA发生了,但线程不知道。又或者链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。

ABA隐患

就像兵法讲的:偷梁换柱、李代桃僵

历史事件:赵氏孤儿

解决ABA问题两种方法:

1、悲观锁思路,加锁;

2、乐观锁思路,通过AtomicStampedReference.class

源码实现,具体看源码:

1. 创建一个Pair类来记录对象引用和时间戳信息,采用int作为时间戳,实际使用的时候时间戳信息要做成自增的,否则时间戳如果重复,还会出现ABA的问题。这个Pair对象是不可变对象,所有的属性都是final的, of方法每次返回一个新的不可变对象。

2. 使用一个volatile类型的引用指向当前的Pair对象,一旦volatile引用发生变化,变化对所有线程可见。

3. set方法时,当要设置的对象和当前Pair对象不一样时,新建一个不可变的Pair对象。

4. compareAndSet方法中,只有期望对象的引用和版本号和目标对象的引用和版本好都一样时,才会新建一个Pair对象,然后用新建的Pair对象和原理的Pair对象做CAS操作。

5. 实际的CAS操作比较的是当前的pair对象和新建的pair对象,pair对象封装了引用和时间戳信息。

Demo:

 @Test
public void test4() {
final int timeStamp = atoReferenceCount.getStamp(); new Thread(() -> {
while(true){
if(atoReferenceCount.compareAndSet(atoReferenceCount.getReference(),
atoReferenceCount.getReference()+1, timeStamp, timeStamp + 1)){
System.out.println("11111111");
break;
}
}
},"线程1:").start(); new Thread(() -> {
while(true){
if(atoReferenceCount.compareAndSet(atoReferenceCount.getReference(),
atoReferenceCount.getReference()+1, timeStamp, timeStamp + 1)){
System.out.println("2222222");
break;
}
}
},"线程2:").start(); try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println(atoReferenceCount.getReference());
}

第二个没有执行,因为时间戳不对了。

修改下代码:

 @Test
public void test4() {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
boolean f = atoReferenceCount.compareAndSet(atoReferenceCount.getReference(),
atoReferenceCount.getReference() + 1, atoReferenceCount.getStamp(),
atoReferenceCount.getStamp() + 1); System.out.println("线程"+Thread.currentThread()+"result="+f);
}, "线程:"+i).start();
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println(atoReferenceCount.getReference());
}

结果:可见线程:0,比较的时候发现时间戳变了,所以没有+1。

demo2:

@Test
public void test5() {
for (int i = 0; i < 4; i++) {
new Thread(() -> {
for (int j = 0; j < 500; j++) {
boolean f = atoReferenceCount.compareAndSet(atoReferenceCount.getReference(),
atoReferenceCount.getReference() + 1, atoReferenceCount.getStamp(),
atoReferenceCount.getStamp() + 1); System.out.println("线程"+Thread.currentThread()+">>j="+j+",result="+f);
}
}, "线程:"+i).start();
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println(atoReferenceCount.getReference());
}

有3次比较时间戳发现已经不同

参考:

https://blog.csdn.net/qq_32998153/article/details/79529704

高并发之CAS机制和ABA问题的更多相关文章

  1. 并发——详细介绍CAS机制

    一.前言   今天花了点时间了解了一下JDK1.8中ConcurrentHashMap的实现,发现它实现的主要思想就是依赖于CAS机制.CAS机制是并发中比较重要的一个概念,所以今天这篇博客就来详细介 ...

  2. 并发之atomicInteger与CAS机制

    并发之atomic与CAS自旋锁 通过前几章的讲解我们知道i++这种类似操作是不安全的.针对这种情况,我们可能会想到利用synchronize关键字实现线程同步,保证++操作的原子性,的确这是一种有效 ...

  3. 深入浅出Java并发包—CAS机制

    在JDK1.5之前.Java主要靠synchronized这个关键字保证同步,已解决多线程下的线程不安全问题,但是这会导致锁的发生,会引发一些个性能问题. 锁主要存在一下问题 (1)在多线程竞争下,加 ...

  4. Java CAS机制详解

    CAS目的: 在多线程中为了保持数据的准确性,避免多个线程同时操作某个变量,很多情况下利用关键字synchronized实现同步锁,使用synchronized关键字修可以使操作的线程排队等待运行,可 ...

  5. CAS机制与自旋锁

    CAS(Compare-and-Swap),即比较并替换,java并发包中许多Atomic的类的底层原理都是CAS. 它的功能是判断内存中某个地址的值是否为预期值,如果是就改变成新值,整个过程具有原子 ...

  6. 什么是CAS机制?(转)

    围绕下面四个点展开叙述: 一:什么是CAS机制? 二:Java当中CAS的底层实现 三:CAS的ABA问题和解决方法 四:java8对CAS的优化 一:什么是CAS机制? 我们先看一段代码: 启动两个 ...

  7. 线程安全之CAS机制详解(分析详细,通俗易懂)

    背景介绍:假设现在有一个线程共享的变量c=0,让两个线程分别对c进行c++操作100次,那么我们最后得到的结果是200吗? 1.在线程不安全的方式下:结果可能小于200,比如当前线程A取得c的值为3, ...

  8. (白话理解)CAS机制

    (白话理解)CAS机制 通过一段对话我们来了解cas用意 示例程序:启动两个线程,每个线程中让静态变量count循环累加100次. 最终输出的count结果是什么呢?一定会是200吗? 加了同步锁之后 ...

  9. 对CAS机制的理解(一)

    先看一段代码:启动两个线程,每个线程中让静态变量count循环累加100次. public class CountTest { public static int count = 0; public ...

随机推荐

  1. 《JAVA设计模式》之装饰模式(Decorator)

    在阎宏博士的<JAVA与模式>一书中开头是这样描述装饰(Decorator)模式的: 装饰模式又名包装(Wrapper)模式.装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替 ...

  2. 将IDEA工程代码提交到Github

    1.git安装配置 1.下载git https://git-scm.com/download/win 2.安装 傻瓜式安装即可,记住安装的目录 3.配置 2.配置git SSH 1.首先申请一个Git ...

  3. 重温位运算、原码、反码、补码、以及>>和<<<区别

    一个例子说明原码,反码,补码: 下面进行5和-5的原码,反码,补码表示: 5的原码:0000 0101 5的反码:0000 0101 5的补码:0000 0101 -5的原码:1000 0101 -5 ...

  4. 洛谷 P1972 [SDOI2009]HH的项链(树状数组,离线)

    传送门 解题思路 因为是求区间的不同种类数,所以我们用树状数组(貌似并没有什么直接联系) (...表示到) 还是和原来一样,用s[i]来表示a[i-lowbit(i)]...a[i]的种类数. 因为有 ...

  5. crack Tut.ReverseMe1.exe

    测试文件:https://www.wocloud.com.cn/webclient/share/sindex.action?id=i9K_Br6TgE7ZLB3oBGUcJmKcRy5TUdZ8U6_ ...

  6. js 程序执行与顺序实现详解

    JavaScript是一种描述型脚本语言,由浏览器进行动态的解析与执行,浏览器对于不同的方式有不同的解析顺序,详细介绍如下,感兴趣的朋友可以参考下哈 函数的声明和调用 JavaScript是一种描述型 ...

  7. openstack stein部署手册 2. 基础应用

    1. chrony # 安装程序包 yum install -y chrony # 变更配置文件 /etc/chrony.conf 增加 server 192.168.123.200 iburst # ...

  8. linux系统安装MongoDB文档

    mongodb文档数据库的安装: wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-4.0.4.tgz tar -zx ...

  9. BJSV-P-004无缝大屏显示

    无缝大屏显示 北京太速科技有限公司在线客服:QQ:448468544 淘宝网站:orihard.taobao.com/?联系电话:15084122580 欢迎关注微信公众号 啊智能时代

  10. JAVA- 内部类及匿名内部类

    普通类,我们平时见到的那种类,就是一个后缀为.java的文件中,直接定义的类,比如 public Cat{ private String name; private int age; } 内部类, 内 ...