第8章 CAS教程
2.3 CAS
2.3.1 CAS是什么?
原子操作新思路
什么是原子操作?
原子操作是执行过程中不被CPU中断的操作,原子操作不会出现多线程交替执行。
比如:
b++、a+=2不具有原子性,多线程在执行时会存在多线程交替执行。
int a=0 具有原子性。
如何将非原子操作实现原子操作?
使用synchronized即可实现:
synchronized(obj){
b++;
}
synchronized(obj){
a+=2;
}b++代码处地临界区中被同步锁保护,多线程执行临界区的代码只能同步执行即串行执行。
有关synchronized实现原子操作的内容可以参考“原子性”章节。
synchronized同步锁也称为悲观锁,它认为总会有线程修改另一个线程的数据造成线程不安全,它将临界区加锁控制,多线程只允许串行执行,这样就保证了一个线程在执行代码的同时不会有线程去交替执行,但是使用悲观锁的性能不高。
有没有一种相比悲观锁方案性能高的实现原子操作的方案呢?
CAS是一种无锁技术,也被称为乐观锁技术。乐观锁认为总是不会有线程去修改另一个线程的数据,所以它在操作时无需使用同步锁即可实现原子操作。CAS依赖CPU的CAS指令在更新数据前先判断数据是否被其它线程修改,如果没有修改则更新。
CAS是什么?
CAS的全称是Compare And Set,即比较并设置(也称为Compare And Swap即比较并交换),CAS从名称上是两个操作(一个比较 一个设置),但它是原子操作,底层由CPU指令支持,将比较和设置实现原子操作。
下面的例子说明 了CAS的操作过程:
下边通过一个例子说明CAS的过程,开发中++操作用的较多,下图是CAS实现++的原子操作:

1、取出期望值,比如求a++,首先取a此时的内存值0。
2、计算目标值,计算a+1的值为1,注意此值是基于a等于0计算得到。
3、调用CAS指令。
4、CAS指令:取出a的当前值和期望值比较,如果相等则用目标值替换当前值。
如果a的期望值与当前值不一致则说明有其它线程将a的值已经更改,转到第一步重新执行。
上图中橙色为CAS指令执行过程,根据CAS的过程可知正确使用CAS的方法如下:
while(true){
//取出期望值
//计算目标值
//调用CAS方法
//判断如果操作成功则退出,否则继续下一轮循环
if(CAS方法(...)){
break;
}
}所以:
CAS操作共需要两个参数:期望值、目标值,目标值是基于期望值计算得出,CAS指令执行时会取出当前内存值与期望值比较如果相等则用目标值更新当前值。
CAS应用场景
CAS是一种无锁实现原子操作的技术,也被称为乐观锁,对于并发高且写多的场景来说,CAS失败的概率很大,会不断的循环执行,导致CPU开销增加。CAS的这种不断循环尝试的过程叫自旋,它会不断重试直到操作成功为止,所以CAS适合读多写少的场景。比如:成绩更正,成绩录入完成后因录入错误需要更正的频率是很少的。
悲观锁适用并发量不大且接受一定延迟的场景,比如:订单退费,非双十一时订单退费的并发量相比下单的并发量少很多,且退费有一定的延迟用户可以接受。
CAS性能
CAS采用无锁技术,其性能高于synchronized同步锁,原因如下:
1、synchronized同步锁使线程串行执行,没有获得锁的线程会进入等待队列阻塞,阻塞解除发生线程切换,上下文切换会耗费性能。
2、通过上边的入门程序可知CAS操作不成功会不断重试直到成功为止,CAS大大减少线程上下文切换的几率。
注意:synchronized从jdk6开始优化,其性能有了很大的提高,所以不要认为synchronized有多么的差,在实际开发要根据应用场景选用适合的技术方案。
2.3.2 CAS快速入门
CAS使用方法
根据CAS的过程总结正确使用CAS的方法如下:
当我们要执行一个CAS操作无论如何都要成功时可以用一个while循环包裹,代码如下:
while(true){
//取出期望值
//计算目标值
//调用CAS方法
//判断如果操作成功则退出,否则继续下一轮循环
if(CAS方法(...)){
break;
}
}如果CAS操作仅执行一次则不需要while循环。
CAS入门程序第一方法
下边使用三种方法完成多线程执行加加操作。
1、首先启动多线程,这里启动100个线程。
2、100个线程对共享变量执行加加操作。

第一种方法不使用同步锁也不使用CAS,本方法是线程不安全的,目的是希望过程到CAS的实现方法。
代码如下:
//cas入门程序,先用线程不安全的方法实现
public static void test_casfirst_unsafe(){
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 1; i <=100; i++) {
threadPool.execute(()->{
sum++;
});
}
shutdown(threadPool);
logger.info("sum="+sum);
}sum的预期结果是100,执行结果与预期不一致,此程序线程不安全。
CAS入门程序第二种方法
第二种方法使用synchronized实现,将sum++放入临界区,多线程执行临界区代码同步执行,线程安全。

代码如下:
//cas入门程序,使用synchornized
public static void test_casfirst_sync(){
ExecutorService threadPool = Executors.newCachedThreadPool();
//锁
Object obj = new Object();
for (int i = 1; i <=100; i++) {
threadPool.execute(()->{
synchronized (obj){
sum++;
}
});
}
shutdown(threadPool);
logger.info("sum="+sum);
}sum的预期结果是100,执行结果与预期一致,程序是线程安全的。
CAS入门程序第三种方法
第三种方法使用CAS实现,通过编写入门程序深刻理解CAS的执行过程。

代码如下:
//cas入门程序,使用cas
public static void test_casfirst_cas(){
ExecutorService threadPool = Executors.newCachedThreadPool();
AtomicInteger sumAtomic = new AtomicInteger(sum);
for (int i = 1; i <=100; i++) {
threadPool.execute(()->{
while(true){
//期望值
int expectValue = sumAtomic.get();
//目标值
int updateValue = expectValue+1;
//使用CAS方法比较并设置
if(sumAtomic.compareAndSet(expectValue, updateValue)){
break;
}
}
});
}
shutdown(threadPool);
sum = sumAtomic.get();
logger.info("sum="+ sum);
}sum的预期结果是100,执行结果与预期一致,程序是线程安全的。
Debug跟踪CAS执行
下边用Debug跟踪CAS执行过程。
在“if(sumAtomic.compareAndSet(expectValue, updateValue))” 打断点,
断点选择"Thread"模式:

Debug运行程序。
100个线程停留在 “if(sumAtomic.compareAndSet(expectValue, updateValue))” 处。

首先让线程“pool-1-thread-1”继续运行,执行成功。

然后再随意让另一个线程继续运行,这里让线程"pool-1-thread-10"运行,执行失败,原因如下:
线程“pool-1-thread-1”将sum更新为1,此时线程"pool-1-thread-10"获取当前值为1与期望值0不匹配执行失败。

继续让线程"pool-1-thread-10"进入下一轮循环执行,如果当前值与期望值相等则设置成功。
2.3.2 AtomicInteger
AtomicInteger概览
原子操作在生产中使用较多,在java.util.concurrent.atomic包下提供了各种原子类,使用这些原子类可以实现针对不同的数据类型进行原子操作,见下图:

下边会分几个视频去讲解这些原子类,下边首先针对基本数据类型的原子类进行讲解。
对基本数据类型的原子操作有:AtomicBoolean、AtomicInteger、AtomicLong、LongAdder、DoubleAdder等类。
下边以AtomicInteger为例进行讲解,其它的基本类型原子类的方法与AtomicInteger差不多,大家可参考API文档自学。
首先查看AtomicInteger的api文档 ,常用方法如下:
构造方法:
AtomicInteger()
创建一个新的AtomicInteger,初始值为 0 。
AtomicInteger(int initialValue)
用给定的初始值创建一个新的AtomicInteger。
原子方法:
int getAndDecrement()
原子操作减1,返回以前的值 。
int decrementAndGet()
原子操作减1,返回更新后的值 。
int getAndincrease()
原子操作加1,返回以前的值。
int increaseAndGet()
原子操作加1,返回更新后的值。
int addAndGet(int delta)
将给定的值原子操作添加到当前值,返回更新后的值。
int get()
获取当前值。
int updateAndGet(IntUnaryOperator updateFunction)
使用给定函数的结果作为原子更新的值,返回更新的值。
boolean compareAndSet(int expect, int update)
如果当前值等于预期值 ,则将原值设置为给定的更新值,成功返回true,失败返回falsecompareAndSet
compareAndSet实现了 “比较并设置” 的原子操作,查看 compareAndSet()方法的源代码,如下:
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}compareAndSet需要两个参数:期望值和目标值。
compareAndSwapLong是一个本地方法:
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);本地方法由C/C++语言实现并由Java代码去调用的方法,本地方法会通过CPU执行CAS操作。
unsafe是一个以更底层的方法进行操作的类,只能由jdk库中的类去使用,通过valueOffset(value属性的偏移量)找到value属性的值。unsafe.compareAndSwapLong方法最终将update目标值更新到value属性中。
首先测试compareAndSet方法:
package com.yjoffer.javase.thread.safe;
import com.yjoffer.javase.config.Logger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 测试Cas
* @author 预见猿份(www.yjoffer.com)
* @version 1.0
**/
public class CasTest {
private static Logger logger = Logger.getLogger(CasTest.class);
//测试compareAndSet
public static void test_compareAndSet(){
//启动两个线程,使用CAS操作a++操作
AtomicInteger a = new AtomicInteger(0);
//t1线程执行cas之前休眠两秒
new Thread(()->{
int expectValue = a.get();
int updateValue = expectValue + 1;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//由于t2线程将a修改导致执行失败
boolean b = a.compareAndSet(expectValue, updateValue);
logger.debug("cas操作结果:"+b);
if(b){
logger.info("输出a的值="+a.get());
}
},"t1").start();
//t2线程执行cas之前不休眠
new Thread(()->{
int expectValue = a.get();
int updateValue = expectValue + 1;
boolean b = a.compareAndSet(expectValue, updateValue);
logger.debug("cas操作结果:"+b);
if(b){
logger.info("输出a的值="+a.get());
}
},"t2").start();
}
...程序执行结果:
10:55:14.608 FINE com.yjoffer.javase.thread.safe.CasTest[t2] - cas操作结果:true
10:55:16.608 FINE com.yjoffer.javase.thread.safe.CasTest[t1] - cas操作结果:false如果当cas操作失败后进行重试则伪代码如下:
while(true){
//取出期望值
//计算目标值
//调用compareAndSet方法
//判断如果操作成功则退出,否则继续下一轮循环
if(compareAndSet(...)){
break;
}
}测试代码如下:
//测试compareAndSet,加while(true)
public static void test_compareAndSet2(){
//启动两个线程,使用CAS操作a++操作
AtomicInteger a = new AtomicInteger(0);
//t1线程执行cas之前休眠两秒
new Thread(()->{
while (true){
int expectValue = a.get();
int updateValue = expectValue + 1;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//由于t2线程将a修改导致执行失败
boolean b = a.compareAndSet(expectValue, updateValue);
logger.debug("cas操作结果:"+b);
if(b){
logger.info("输出a的值="+a.get());
break;
}
}
},"t1").start();
//t2线程执行cas之前不休眠
new Thread(()->{
while (true) {
int expectValue = a.get();
int updateValue = expectValue + 1;
boolean b = a.compareAndSet(expectValue, updateValue);
logger.debug("cas操作结果:" + b);
if (b) {
logger.info("输出a的值=" + a.get());
break;
}
}
},"t2").start();
}输出 :
10:59:22.319 FINE com.yjoffer.javase.thread.safe.CasTest[t2] - cas操作结果:true
10:59:22.344 INFO com.yjoffer.javase.thread.safe.CasTest[t2] - 输出a的值=1
10:59:24.318 FINE com.yjoffer.javase.thread.safe.CasTest[t1] - cas操作结果:false
10:59:26.318 FINE com.yjoffer.javase.thread.safe.CasTest[t1] - cas操作结果:true
10:59:26.318 INFO com.yjoffer.javase.thread.safe.CasTest[t1] - 输出a的值=2从输出可以看出t1线程执行第一次CAS失败,重试后成功。
decrementAndGet
decrementAndGet方法实现减一操作,下边四个方法相似:
int getAndDecrement()
原子操作减1,返回以前的值 。
int decrementAndGet()
原子操作减1,返回更新后的值 。
int getAndincrease()
原子操作加1,返回以前的值。
int increaseAndGet()
原子操作加1,返回更新后的值。查看 decrementAndGet方法的源代码,如下:
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}getAndAddInt源代码如下:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//获取期望值
var5 = this.getIntVolatile(var1, var2);
//目标值为var5 + var4
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}在getAndAddInt方法中可以看出当CAS操作不成功会不断循环重试,直到成功为止。
下边的代码测试decrementAndGet(),启动100个线程执行加1,启动100个线程执行减1。
package com.yjoffer.javase.thread.safe;
import com.yjoffer.javase.config.Logger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* CAS测试
* @author 预见猿份(www.yjoffer.com)
* @version 1.0
**/
public class CasTest {
private static Logger logger = Logger.getLogger(CasTest.class);
//测试decrementAndGet
public static void test_decrementAndGet(){
ExecutorService threadPool = Executors.newCachedThreadPool();
AtomicInteger sumAtomic = new AtomicInteger(sum);
for (int i = 1; i <=100; i++) {
threadPool.execute(()->{
//减1
sumAtomic.decrementAndGet();
});
}
for (int i = 1; i <=100; i++) {
threadPool.execute(()->{
//加1
sumAtomic.increaseAndGet();
});
}
shutdown(threadPool);
sum = sumAtomic.get();
logger.info("sum="+ sum);
}
public static void main(String[] args) throws InterruptedException {
test_decrementAndGet();
}
}运行程序,输出 sum=0,结果 正确。
addAndGet
addAndGet方法实现了加上一个具体的值,方法返回结果是相加后的值,它的源代码如下:
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}getAndAddInt的源代码如下:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//获取期望值
var5 = this.getIntVolatile(var1, var2);
//目标值为var5 + var4
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}上边的程序使用addAndGet实现如下:
//测试addAndGet
public static void test_addAndGet(){
ExecutorService threadPool = Executors.newCachedThreadPool();
AtomicInteger sumAtomic = new AtomicInteger(sum);
for (int i = 1; i <=100; i++) {
threadPool.execute(()->{
//减1
sumAtomic.addAndGet(-1);
});
}
for (int i = 1; i <=100; i++) {
threadPool.execute(()->{
//加1
sumAtomic.addAndGet(1);
});
}
shutdown(threadPool);
sum = sumAtomic.get();
logger.info("sum="+ sum);
}运行程序,输出 sum=0,结果 正确。
updateAndGet
updateAndGet方法接收Lambda表达式,根据表达式的结果去更新值,更新成功则退出方法,否则重试直到成功为止。
查看源代码如下:
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev, next;
do {
//取出期望值
prev = get();
//通过函数式接口计算目标值
next = updateFunction.applyAsInt(prev);
//比较并设置,如果成功则退出否则继续重试
} while (!compareAndSet(prev, next));
return next;
}IntUnaryOperator是一个函数式接口,抽象方法applyAsInt接收一个整数值 返回一个整数值 。
源代码如下:
@FunctionalInterface
public interface IntUnaryOperator {
/**
* Applies this operator to the given operand.
*
* @param operand the operand
* @return the operator result
*/
int applyAsInt(int operand);
...
}将上边的程序用updateAndGet实现如下:
//测试updateAndGet
public static void test_updateAndGet(){
ExecutorService threadPool = Executors.newCachedThreadPool();
AtomicInteger sumAtomic = new AtomicInteger(sum);
for (int i = 1; i <=100; i++) {
threadPool.execute(()->{
//减1
sumAtomic.updateAndGet(v->v-1);
});
}
for (int i = 1; i <=100; i++) {
threadPool.execute(()->{
//加1
sumAtomic.updateAndGet(v->v+1);
});
}
shutdown(threadPool);
sum = sumAtomic.get();
logger.info("sum="+ sum);
}2.3.3 AtomicReference
对象引用更新的问题
在前边的课程中我们举的例子都是多线程更新一个基本类型的共享变量存在线程不安全的问题,多线程去并发更新一个对象的引用时也会存在线程不安全的问题。
例如:
下边程序实现了向同一个学生奖励小红花的过程,启动100个线程,每个线程向该学生奖励一个小红花,预期共奖励100个小红花。代码如下:
package com.yjoffer.javase.thread.safe;
import com.yjoffer.javase.config.Logger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* CAS测试
* @author 预见猿份(www.yjoffer.com)
* @version 1.0
**/
public class CasTest {
private static Logger logger = Logger.getLogger(CasTest.class);
/**
* 学生类
*/
static class PbStudent{
/**
* 名称
*/
String name;
/**
* 小红花数量
*/
int flowerNum;
public PbStudent(String name,int flowerNum){
this.name = name;
this.flowerNum = flowerNum;
}
public String getName() {
return name;
}
public int getFlowerNum() {
return flowerNum;
}
}
static volatile PbStudent stu = new PbStudent("小创", 0);
//测试奖励小红花
public static void test_RedFlower(){
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
threadPool.execute(()->{
int flowerNum = stu.getFlowerNum();
stu = new PbStudent("小创",flowerNum+1);
});
}
shutdown(threadPool);
logger.debug("小红花数量="+stu.getFlowerNum());
}运行程序,小红花的数量和预期不一致,程序是线程不安全的。
原因在哪?
每个线程先从引用中取出当前小红花数量,再将新对象赋值给stu引用。这个过程不是原子操作,多线程可以交替执行,如下图:

1、线程1执行int flowerNum = stu.getFlowerNum();得到flowerNum=10,然后让出CPU。
2、线程2分配CPU时间片执行int flowerNum = stu.getFlowerNum();得到flowerNum=10。
3、线程2将new PbStudent(小创,11)赋值给stu,让出CPU。
4、线程1执行stu = new PbStudent("小创",flowerNum+1);,将new PbStudent(小创,11)赋值给stu。
线程1覆盖了线程2的stu引用,这里少加一次小红花。
同步锁解决问题
下边使用synchronized解决对象引用更新的问题,每个线程的执行逻辑放在临界区中具有原子性,这样即不存在多线程交替执行的问题,代码如下:
//测试奖励小红花,加同步锁
public static void test_RedFlower_sync(){
ExecutorService threadPool = Executors.newCachedThreadPool();
//定义锁对象
Object obj = new Object();
for (int i = 0; i < 100; i++) {
threadPool.execute(()->{
synchronized (obj){
int flowerNum = stu.getFlowerNum();
stu = new PbStudent("小创",flowerNum+1);
}
});
}
shutdown(threadPool);
logger.debug("小红花数量="+stu.getFlowerNum());
}执行程序,预期和结束一致,小红花数量为100。
AtomicReference解决问题
AtomicReference提供CAS方法更新对象引用,AtomicReference的API如下:

compareAndSet正是用CAS实现对象引用的更新。测试代码如下:
//测试AtomicReference的CompareAndSet方法
public static void test_AtomicReference(){
AtomicReference<PbStudent> atomicStu = new AtomicReference<>(stu);
//启动两个线程更新,只会有一个线程更新成功
new Thread(()->{
PbStudent expectValue = atomicStu.get();
int flowerNum = expectValue.getFlowerNum();
PbStudent updateValue = new PbStudent("小创", flowerNum + 1);
// try {
// Thread.sleep(2000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
boolean b = atomicStu.compareAndSet(expectValue, updateValue);
if(b){
logger.debug("更新成功"+updateValue.toString());
}
},"t1").start();
new Thread(()->{
PbStudent expectValue = atomicStu.get();
int flowerNum = expectValue.getFlowerNum();
PbStudent updateValue = new PbStudent("小创", flowerNum + 1);
boolean b = atomicStu.compareAndSet(expectValue, updateValue);
if(b){
logger.debug("更新成功"+updateValue.toString());
}
},"t2").start();
}执行程序,只会有一个线程更新成功。
如果把t1线程中屏蔽的sleep代码段放开注释则一定是t2线程更新成功。
如果考虑无论如何CAS必须执行成功则需要添加while(true),当CAS失败后重试。t1线程的代码更改如下:
new Thread(()->{
while (true){
PbStudent expectValue = atomicStu.get();
int flowerNum = expectValue.getFlowerNum();
PbStudent updateValue = new PbStudent("小创", flowerNum + 1);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = atomicStu.compareAndSet(expectValue, updateValue);
if(b){
logger.debug("更新成功"+updateValue.toString());
break;
}
}
},"t1").start();运行程序,t1线程最终也会更新成功。
运行程序输出 如下:
21:32:36.793 FINE com.yjoffer.javase.thread.safe.CasTest[t2] - 更新成功PbStudent{name='小创', flowerNum=1}
21:32:40.792 FINE com.yjoffer.javase.thread.safe.CasTest[t1] - 更新成功PbStudent{name='小创', flowerNum=2}AtomicReference的应用场景
当多线程去更新引用变量,新对象状态是基于老对象的状态得出,此时需要使用AtomicReference保证每个线程更新引用的原子性,否则将会出现多线程交替执行导致线程不安全。
但是,如果新对象的状态不依赖老对象的状态此时则不需要使用AtomicReference保证更新引用的原子性。
下边的例子模拟用户登录,共享变量表示当前登录成功的用户,当前只会有一个用户登录成功,每个线程模拟一个用户登录。
代码如下:
static class ActiveUser{
String name;
public ActiveUser(String name){
this.name = name;
}
@Override
public String toString() {
return "ActiveUser{" +
"name='" + name + '\'' +
'}';
}
}
static ActiveUser activeUser;
public static void test_login(){
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 1; i <=10; i++) {
int temp = i;
threadPool.execute(()->{
ActiveUser user = new ActiveUser("user" + temp);
activeUser = user;
});
}
shutdown(threadPool);
logger.info(activeUser.toString());
}
public static void main(String[] args) throws InterruptedException {
test_login();
}上边程序中,多线程对共享变量的更新并不是基于上一个对象状态得出,哪一个线程先执行就表示先进行登录,登录成功更新共享变量的值即可 。所以下边的代码不需要 保证原子性,不用采用AtomicReference。
ActiveUser user = new ActiveUser("user" + temp);
activeUser = user;2.3.4 什么是ABA问题
什么是ABA问题?
ABA问题是:有一个需求将共享变量由A更新到B,再由B更新到A,当采用多线程并且使用CAS去更新后存在线程不安全的问题,这就是ABA问题。
执行过程如下图:

1、将共享变量由A更新为B,启动两个线程执行此操作。
2、线程1获取期望值A,开始休眠。
3、线程2获取期望值A,执行CAS将A更新为B。
4、线程2获取期望值B,执行CAS将B更新为A。
5、线程1继续运行,执行CAS将A更新为B。
根据需求预期共享变量最终为A,执行结果共享为B,预期与结果不一致存在线程不安全的问题。
举个银行转账的例子:

1、向账户存入100元,由于系统问题启动两个线程执行此操作。
2、线程1获取原账户金额100,开始休眠。
3、线程2获取原账户金额100,执行cas存入100元,此时账户金额200元。
4、线程2从账户取100元,执行cas取出100元,此时账户金额100元。
5、线程1休眠结束继续运行,执行cas存入100元,此时账户金额200元。
最后一步的线程1执行存入100元为重复操作,预期账户金额为100元,结果为200元,存在线程不安全的问题。
最后总结ABA问题:
ABA问题就是:一个共享变量两次读取相同的值,因为两次读取的值相同则认为该值没有发生,实际上该值在两次读取之间发生了变化,仅仅根据值是否相同来判断是否发生变化在某些场景下是存在线程安全问题的。
程序还原ABA问题
下边的程序演示了下图的ABA问题:

代码如下:
package com.yjoffer.javase.thread.safe;
import com.yjoffer.javase.config.Logger;
import java.math.BigDecimal;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* CAS测试
* @author 预见猿份(www.yjoffer.com)
* @version 1.0
**/
public class CasTest {
private static Logger logger = Logger.getLogger(CasTest.class);
/**
* 测试ABA问题
* 1、原账户金额为100元。
* 2、存入100元
* 3、取出100元。
*/
public static void test_aba(){
//原账户为100元
AtomicInteger account = new AtomicInteger(100);
//线程t1存入100元
Thread t1 = new Thread(() -> {
//存入100元
int expectValue = account.get();
int updateValue = expectValue+100;
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (account.compareAndSet(expectValue, updateValue)) {
logger.debug("存入100元成功,余额"+account.get());
} else {
logger.debug("存入100元失败");
}
}, "t1");
//线程t2存入100元,取出100元
Thread t2 = new Thread(() -> {
//存入100元
int expectValue = account.get();
int updateValue = expectValue+100;
if (account.compareAndSet(expectValue, updateValue)) {
logger.debug("存入100元成功,余额"+account.get());
} else {
logger.debug("存入100元失败");
}
//取出100元
int expectValue2 = account.get();
int updateValue2 = expectValue2-100;
if (account.compareAndSet(expectValue2, updateValue2)) {
logger.debug("取出100元成功,余额"+account.get());
} else {
logger.debug("取出100元失败");
}
}, "t2");
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("余额="+account.get());
}
public static void main(String[] args) throws InterruptedException {
test_aba();
}
}运行程序,输出如下:
07:11:48.309 FINE com.yjoffer.javase.thread.safe.CasTest[t2] - 存入100元成功,余额200
07:11:48.333 FINE com.yjoffer.javase.thread.safe.CasTest[t2] - 取出100元成功,余额100
07:11:51.309 FINE com.yjoffer.javase.thread.safe.CasTest[t1] - 存入100元成功,余额200
07:11:51.310 INFO com.yjoffer.javase.thread.safe.CasTest[main] - 余额=200三次CAS操作全部成功,预期账户余额为100元,结果不正确。
2.3.5 AtomicStampedReference
解决ABA问题的思路
回顾ABA问题是什么:有一个需求将共享变量由A更新到B,再由B更新到A,当采用多线程并且使用CAS去更新后存在线程不安全的问题,这就是ABA问题。
执行过程如下图:

1、将共享变量由A更新为B,启动两个线程执行此操作。
2、线程1获取期望值A,开始休眠。
3、线程2获取期望值A,执行CAS将A更新为B。
4、线程2获取期望值B,执行CAS将B更新为A。
5、线程1继续运行,执行CAS将A更新为B。
根据需求预期共享变量最终为A,执行结果共享为B,预期与结果不一致存在线程不安全的问题。
存在ABA问题的关键是如区分数据是否发生变化,不能仅根据值是否相同来判断是否发生变化,如果在每一次更新数据时加入版本号根据值和版本号两个元素来判断是否相同,如下:1A,2B,3A。
如下图:

1、线程1取出期望值1A,休眠。
2、线程2取出期望值1A,执行CAS,将1A更新为2B。
3、线程2取出期望值2B,执行CAS,将2B更新为3A。
4、线程1休眠结束,执行CAS将1A更新为2B失败,因为当前值为3A与期望值1A不一致。
AtomicStampedReference解决ABA问题
AtomicStampedReference是JUC包中的一个原子类,它可以解决ABA问题,每次执行CAS操作先判断当前值的版本号与期望值是否一致,如果一致再进行更新操作,如下图:

1、取出期望值及对应的版本号。
2、计算目标值及目标版本号。
3、调用CAS指令,接收期望值及对应版本号、目标值及新版本号。
4、取出当前值及对应的版本号。
5、如果期望值与当前值一致,期望值版本号与当前版本号一致则更新,将当前值更新为目标值,当前值版本更新为目标值版本。如果不一致则CAS执行失败,失败后根据需求可以重试。
AtomicStampedReference与AtomicReference的不同是AtomicStampedReference增加了版本号的比较。
AtomicStampedReference的API如下:

compareAndSet方法的定义如下:
参数:
expectedReference - 预期值
newReference - 目标值
expectedStamp - 预期值版本号
newStamp - 目标值版本号
返回值:
true:更新成功
false:更新失败
boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)下边使用AtomicStampedReference解决上边的ABA问题,代码如下:
/**
* 解决ABA问题
* 1、原账户金额为100元。
* 2、存入100元
* 3、取出100元。
*/
public static void test_AtomicStampedReference(){
//原账户为100元,初始为100元,版本号为1
AtomicStampedReference<Integer> account = new AtomicStampedReference<>(100, 1);
//线程t1存入100元
Thread t1 = new Thread(() -> {
//存入100元
//期望值
Integer expectValue = account.getReference();
//期望值版本号
int expectStamp = account.getStamp();
//目标值
Integer updateValue = expectValue+100;
//目标值版本号
int updateStamp = expectStamp +1;
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (account.compareAndSet(expectValue,updateValue,expectStamp,updateStamp)) {
logger.debug("存入100元成功,余额"+account.getReference());
} else {
logger.debug("存入100元失败");
}
}, "t1");
//线程t2存入100元,取出100元
Thread t2 = new Thread(() -> {
//存入100元
//期望值
Integer expectValue = account.getReference();
//期望值版本号
int expectStamp = account.getStamp();
//目标值
Integer updateValue = expectValue+100;
//目标值版本号
int updateStamp = expectStamp +1;
if (account.compareAndSet(expectValue,updateValue,expectStamp,updateStamp)) {
logger.debug("存入100元成功,余额"+account.getReference());
} else {
logger.debug("存入100元失败");
}
//取出100元
Integer expectValue2 = account.getReference();
int expectStamp2 = account.getStamp();
Integer updateValue2 = expectValue2-100;
int updateStamp2 = expectStamp2+1;
if (account.compareAndSet(expectValue2, updateValue2,expectStamp2,updateStamp2)) {
logger.debug("取出100元成功,余额"+account.getReference());
} else {
logger.debug("取出100元失败");
}
}, "t2");
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("余额="+account.getReference());
}运行程序,输出 :
11:31:02.967 FINE com.yjoffer.javase.thread.safe.CasTest[t2] - 存入100元成功,余额200
11:31:02.990 FINE com.yjoffer.javase.thread.safe.CasTest[t2] - 取出100元成功,余额100
11:31:05.967 FINE com.yjoffer.javase.thread.safe.CasTest[t1] - 存入100元失败
11:31:05.967 INFO com.yjoffer.javase.thread.safe.CasTest[main] - 余额=100线程"t1"由于版本号不正确导致CAS操作失败,最后余额为100结果正确。
2.3.5 AtomicStampedReference转账案例
案例需求
1、写一个UserAccount账户类,增加转账方法addamount(float amount)、subtrantAmount(float amount)并使用AtomicStampedReference解决ABA问题
2、定义转账接口
再设计一个任务类用于操作转账接口,转账任务由多线程去执行,所以任务类实现了转账接口和Runnable接口
3、使用多线程执行转账任务
4、避免多线程重复执行同一个任务
案例实现
1、首先回顾AtomicStampedReference的使用方法
AtomicStampedReference除了比较值还增加了版本号的比较 ,应用方法如下:
1、取出期望值
2、取出期望值版本号
3、计算目标值
4、计算目标版本号
5、调用compareAndSet方法2、定义账户类:
package com.yjoffer.javase.thread.bank_aba;
import com.yjoffer.javase.config.Logger;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* 账户类
* @author 预见猿份(www.yjoffer.com)
* @version 1.0
**/
public class UserAccount {
private static Logger logger = Logger.getLogger(UserAccount.class);
/**
* 账户名称
*/
String name;
/**
* 账户余额
*/
AtomicStampedReference<Float> atomicbalance;
public UserAccount(String name,float balance){
this.name = name;
atomicbalance = new AtomicStampedReference<Float>(balance,1);
}
//增加余额
public void addAmount(float amount){
if(amount<=0){
throw new RuntimeException("参数非法");
}
while (true){
//期望值
Float balance = atomicbalance.getReference();
//期望值版本号
int stamp = atomicbalance.getStamp();
//目标值
float newBalance = balance +amount;
//目标版本号
int newStamp = stamp +1;
boolean b = atomicbalance.compareAndSet(balance, newBalance, stamp, newStamp);
if(b){
break;
}
}
}
//减去余额
public void subtractAmount(float amount){
if(amount<=0){
throw new RuntimeException("参数非法");
}
while (true){
Float balance = atomicbalance.getReference();
if(balance<amount){
throw new RuntimeException("余额不足");
}
//期望值版本号
int stamp = atomicbalance.getStamp();
//目标值
float newBalance = balance -amount;
//目标版本号
int newStamp = stamp +1;
boolean b = atomicbalance.compareAndSet(balance, newBalance, stamp, newStamp);
if(b){
break;
}
}
}
public String getName() {
return name;
}
public float getBalance() {
return atomicbalance.getReference();
}
}2、定义转账接口
package com.yjoffer.javase.thread.bank_aba;/**
* Created by Administrator.
*/
/**
* 转账接口
* @author 预见猿份(www.yjoffer.com)
* @version 1.0
**/
public interface TransferCasInterface {
/**
* 转账方法
* @param from 源账户
* @param target 目标账户
* @param amount 转账金额
*/
void transfer(UserAccount from, UserAccount target, float amount);
}3、定义转账任务类
package com.yjoffer.javase.thread.bank_aba;
import com.yjoffer.javase.config.Logger;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* 转账任务类
* @author 预见猿份(www.yjoffer.com)
* @version 1.0
**/
public class TransferCasTask implements TransferCasInterface,Runnable {
private static Logger logger = Logger.getLogger(TransferCasTask.class);
//防止任务重复执行
private AtomicStampedReference commitMark = new AtomicStampedReference(0, 0);
/**
* 源账户
*/
private UserAccount from;
/**
* 目标账户
*/
private UserAccount target;
/**
* 转账金额
*/
private float amount;
public TransferCasTask(UserAccount from, UserAccount target, float amount){
this.from = from;
this.target = target;
this.amount = amount;
}
@Override
public void run() {
//转账
transfer(from,target,amount);
}
public void transfer(UserAccount from, UserAccount target, float amount) {
//防止多线程重复执行任务
if(commitMark.compareAndSet(0,1,0,1)){
try {
//源账户扣钱
from.subtractAmount(amount);
//目标账户加钱
target.addAmount(amount);
logger.debug(from.getName()+"---->"+target.getName()+"|"+amount);
} catch (Exception e) {
logger.debug(from.getName()+"---->"+target.getName()+"|失败");
}
}else{
logger.debug(from.getName()+"---->"+target.getName()+"|失败");
}
}
}4、定义测试类
package com.yjoffer.javase.thread.bank_aba;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* 转账测试
* @author 预见猿份(www.yjoffer.com)
* @version 1.0
**/
public class TranserCasTest {
//转账10次,每次100元
public static void test1(){
//源账户
UserAccount from = new UserAccount( "张三", 1000);
//目标账户
UserAccount target = new UserAccount("李四", 1000);
//线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
//张三向李向转账10次
//启动10个线程转账,每个线程转一次
for (int i = 0; i < 10; i++) {
//每次转100元
threadPool.execute(new TransferCasTask(from,target,100));
}
//李四向张三转账10次
//启动10个线程转账,每个线程转一次
for (int i = 0; i < 10; i++) {
//每次转100元
threadPool.execute(new TransferCasTask(target,from,100));
}
shutdown(threadPool);
System.out.println("from:"+from.getBalance());
System.out.println("target:"+target.getBalance());
}
//转账10次,每次100元,测试任务重复性
public static void test2(){
//源账户
UserAccount from = new UserAccount( "张三", 1000);
//目标账户
UserAccount target = new UserAccount("李四", 1000);
//线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
//创建转账任务
TransferCasTask transferCasTask = new TransferCasTask(from, target, 100);
//张三向李向转账1次,重复提交10次,启动10个线程转账,每个线程转一次
for (int i = 0; i < 10; i++) {
threadPool.execute(transferCasTask);
}
shutdown(threadPool);
System.out.println("from:"+from.getBalance());
System.out.println("target:"+target.getBalance());
}
public static void main(String[] args) {
test2();
}
//等待线程池中的线程完成
private static void shutdown(ExecutorService threadPool) {
//线程池结束
threadPool.shutdown();
while (!threadPool.isTerminated()) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}执行test1避免ABA:
from:1000.0
target:1000.0执行test2避免任务重复执行:
from:900.0
target:1100.02.3.6 AtomicMarkableReference入门
AtomicMarkableReference介绍
AtomicMarkableReference和AtomicStampedReference一样都可以解决ABA问题(关于ABA问题请参考“AtomicStampedReference”章节),两者的区别如下:
AtomicStampedReference的版本号是整型,它关心每一次的改动,每次操作生成不同的版本号;AtomicMarkableReference的版本号是布尔型,它只关心是否有人改动,版本号只有false、true两种。
AtomicMarkableReference的构造方法如下:
AtomicMarkableReference(V initialRef, boolean initialMark)
用给定的初始值创建一个新的 AtomicMarkableReference 。可以看出,AtomicMarkableReference的版本号变为了布尔类型,记录的状态要么是true,要么是false。
AtomicMarkableReference测试
因为AtomicMarkableReference记录的状态为true或false,可以用AtomicMarkableReference实现多线程争抢,未抢到的状态为false,抢到时的状态为true,代码如下:
package com.yjoffer.javase.thread.safe;
import com.yjoffer.javase.config.Logger;
import java.math.BigDecimal;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.*;
/**
* CAS测试
* @author 预见猿份(www.yjoffer.com)
* @version 1.0
**/
public class CasTest {
private static Logger logger = Logger.getLogger(CasTest.class);
//测试AtomicMarkableReference
public static void test_AtomicMarkableReference(){
AtomicMarkableReference atomicMarkableReference = new AtomicMarkableReference(100, false);
ExecutorService threadPool = Executors.newCachedThreadPool();
//5个线程争抢一个CAS操作
for (int i = 0; i < 5; i++) {
threadPool.execute(()->{
if(atomicMarkableReference.compareAndSet(100,110,false,true)){
logger.debug("执行成功");
}else{
logger.debug("执行失败");
}
});
}
shutdown(threadPool);
}
public static void main(String[] args) throws InterruptedException {
test_AtomicMarkableReference();
}
}运行测试,每次只有一个线程执行CAS成功,状态为true,其它线程执行失败状态为false。
2.3.7 AtomicMarkableReference案例
AtomicMarkableReference应用
根据AtomicMarkableReference的特点,下边实现抢红包的例子,当红包创建后有多个线程去争抢,只会有一个线程抢到红包。
代码如下:
1、首先创建一个发红包的工厂
package com.yjoffer.javase.thread.safe;
import com.yjoffer.javase.config.Logger;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicMarkableReference;
/**
* 红包工厂
* @author 预见猿份(www.yjoffer.com)
* @version 1.0
**/
public class RedEnvelopeFactory {
private static Logger logger = Logger.getLogger(RedEnvelopeFactory.class);
/**
* 红包类
*/
public static class RedEnvelope {
private int amount;
private AtomicMarkableReference<RedEnvelope> redPacketMarkable;
private RedEnvelope(int amount){
this.amount = amount;
this.redPacketMarkable = new AtomicMarkableReference<RedEnvelope>(this,false);
}
//抢红包
private RedEnvelope mark(){
if(redPacketMarkable.compareAndSet(this,this,false,true)){
return this;
}else{
return null;
}
}
public int getAmount(){
return amount;
}
}
private static volatile RedEnvelope redEnvelope;
//红包生成器
private static Thread creater = new Thread(()->{
while (true){
redEnvelope = new RedEnvelope(new Random().nextInt(100));
logger.debug("发红包啦!!!");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
static {
creater.start();
}
public static RedEnvelope open(){
if(redEnvelope!=null){
return redEnvelope.mark();
}else{
return null;
}
}
}2、测试多线程抢红包
package com.yjoffer.javase.thread.safe;
import com.yjoffer.javase.config.Logger;
import java.math.BigDecimal;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.*;
/**
* CAS测试
* @author 预见猿份(www.yjoffer.com)
* @version 1.0
**/
public class CasTest {
private static Logger logger = Logger.getLogger(CasTest.class);
//测试抢红包
public static void test_RedEnvelopeFactory(){
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int j = 0; j < 10; j++) {
for (int i = 0; i < 100; i++) {
threadPool.execute(()->{
RedEnvelopeFactory.RedEnvelope redEnvelope = RedEnvelopeFactory.open();
if(redEnvelope != null){
logger.debug("抢到啦!"+redEnvelope.getAmount());
}
});
}
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
shutdown(threadPool);
}
public static void main(String[] args) throws InterruptedException {
test_RedPacketFactory();
}
}运行测试方法:
22:08:45.275 FINE com.yjoffer.javase.thread.safe.RedEnvelopeFactory[Thread-1] - 发红包啦!!!
22:08:48.274 FINE com.yjoffer.javase.thread.safe.CasTest[pool-1-thread-15] - 抢到啦!65
22:08:48.293 FINE com.yjoffer.javase.thread.safe.RedEnvelopeFactory[Thread-1] - 发红包啦!!!
22:08:51.274 FINE com.yjoffer.javase.thread.safe.CasTest[pool-1-thread-19] - 抢到啦!62
22:08:51.293 FINE com.yjoffer.javase.thread.safe.RedEnvelopeFactory[Thread-1] - 发红包啦!!!
22:08:54.275 FINE com.yjoffer.javase.thread.safe.CasTest[pool-1-thread-19] - 抢到啦!51
22:08:54.293 FINE com.yjoffer.javase.thread.safe.RedEnvelopeFactory[Thread-1] - 发红包啦!!!
22:08:57.276 FINE com.yjoffer.javase.thread.safe.CasTest[pool-1-thread-22] - 抢到啦!88
22:08:57.293 FINE com.yjoffer.javase.thread.safe.RedEnvelopeFactory[Thread-1] - 发红包啦!!!
22:09:00.277 FINE com.yjoffer.javase.thread.safe.CasTest[pool-1-thread-19] - 抢到啦!54
22:09:00.293 FINE com.yjoffer.javase.thread.safe.RedEnvelopeFactory[Thread-1] - 发红包啦!!!
...2.3.6 原子数组入门
原子数组介绍
多线程对数组元素进行写操作也存在线程安全问题,比如一个整型数组,多线程对数组元素进行自增操作,在没有作同步处理等措施时这是线程不安全的。
在java.util.concurrent.atomic包下提供以下类对数组进行原子操作:
AtomicIntegerArray :整型原子数组
AtomicLongArray: 长整型原子数组
AtomicReferenceArray: 对象引用类型原子数组
原子数组测试
下边测试原子数组保证线程安全的现象。
首先用多线程操作一个普通数组,下边的代码实现100个线程分别向数组的第0号元素自增操作:
package com.yjoffer.javase.thread.safe;
import com.yjoffer.javase.config.Logger;
import java.math.BigDecimal;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.*;
/**
* Cas测试
* @author 预见猿份(www.yjoffer.com)
* @version 1.0
**/
public class CasTest {
private static Logger logger = Logger.getLogger(CasTest.class);
//测试非原子数组的线程安全性
public static void test_array(){
int[] nums = new int[10];
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
threadPool.execute(()->{
nums[0]++;
});
}
shutdown(threadPool);
logger.debug(String.valueOf(nums[0]));
}
public static void main(String[] args) throws InterruptedException {
test_array();
}
}按照预期nums数组的第0号元素自增100次的值为100,运行程序结果不正确。
下边使用AtomicIntegerArray测试上边的程序:
//测试原子数组
public static void test_atomicArray(){
AtomicIntegerArray nums = new AtomicIntegerArray(10);
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
threadPool.execute(()->{
nums.getAndincrease(0);
});
}
shutdown(threadPool);
logger.debug(String.valueOf(nums.get(0)));
}运行程序结果正确。
