目录
什么是CAS
CAS(Compare And Swap):比较并交换,CAS是乐观锁的一种实现
一个CAS涉及到的操作
假设内存中的原数据V,旧的预期值A,要修改的的值B
1.比较V与A的值是否相等(Compare)
2.如果比较相等,则将B写入V(Swap)
3.返回操作是否成功
简单的说,CAS就是直接尝试修改变量,再返回修改的结果.比较读和写两个时间点主存中变量的值是否相等,如果相等就把要修改的值写回主存中,如果不相等就不做任何操作,修改成功返回true,修改失败就返回false
CAS的实现原理
Java中的CAS是基于Unsafe这个类提供的CAS操作 ,Unsafe中的CAS又是基于操作系统和CPU提供的机制来实现的,即硬件予以支持,软件层面才能做到
CAS的ABA问题
什么是ABA问题
假设有两个线程,t1和t2,有一个共享变量num,初始值为A,t1线程想要使用CAS将A修改为Z,此时t1需要进行如下操作:先从主存中读取num的值并记录下来,之后使用CAS判定当前num的值是否为A,如果为A就修改为Z。但是在这个过程中可能t2线程先将num的值修改为B,又将num的值从B修改回A,这就是CAS的ABA问题
代码示例(模拟CAS的ABA问题)
利用sleep方法来简单模拟ABA问题:
1、t1线程想要修改num的为5,首先t1线程从主存中读取num的值并记录为旧的预期值,此时旧的预期值为2
2、在t1线程开始修改操作之前,t2线程先将主存中的num改为15,再将num从15改回2,此时线程2执行结束退出
3、t1线程继续执行修改操作,从主存中读取到当前num值为2,与预期值相等,就进行修改操作.
package threadhomework;
/**
* Created with IntelliJ IDEA.
* Description: ABA问题示例
* User: Li_yizYa
* Date: 2022—09—24
* Time: 13:23
*/
public class CASDemo {
private static int num = 2;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
int oldNum = num;//旧的预期值
//利用sleep方法模拟t2在t1获取到共享变量后完成两次修改操作
Thread.sleep(200);
//如果旧的预期值与当前num的值相等则修改
if(oldNum == num) {
num = 5;
System.out.println("t1线程修改成功,修改后num的值为: " + num);
} else {
System.out.println("t1线程修改失败");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
num = 15;
System.out.println("t2线程第一次修改num,修改为: " + num);
num = 2;
System.out.println("t2线程第二次修改num,修改为: " + num);
}
});
t1.start();
//保证t1线程先执行
Thread.sleep(100);
t2.start();
}
}
解决方案
给要修改的值引入版本号,在CAS比较数据当前值和旧值的同时,也要比较版本号是否符合预期.
如果旧值与当前值相同,则开始比较版本号:
· 如果当前版本号与旧的预期版本号相同,则进行修改,版本号+1
· 如果当前版本号与旧的预期版本号不相同,则修改失败.
在Java标准库中提供了AtomicStampedReference<E>类,这个类可以对某个类进行包装,在内部就提供了版本管理的功能.
例如上述ABA问题演示代码,再加上一个版本号就可以解决ABA问题
package threadhomework;
/**
* Created with IntelliJ IDEA.
* Description: ABA问题示例
* User: Li_yizYa
* Date: 2022—09—24
* Time: 13:23
*/
public class CASDemo {
private static int num = 2;
private static int version = 1;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
int oldNum = num;
int oldVersion = version;
Thread.sleep(200);
if(oldNum == num && oldVersion == version) {
num = 5;
//修改成功,版本号+1
version++;
System.out.println("t1线程修改成功,修改后num的值为: " + num + " 当前版本号为: " + version);
} else {
System.out.println("t1线程修改失败,版本号不匹配 旧的预期版本号为: " + oldVersion + " 当前版本号为: " + version);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
num = 15;
version++;//修改成功,版本号+1
System.out.println("t2线程第一次修改num,修改为: " + num + " 当前版本号为: " + version);
num = 2;
version++;//修改成功,版本号+1
System.out.println("t2线程第二次修改num,修改为: " + num + " 当前版本号为: " + version);
}
});
t1.start();
Thread.sleep(100);
t2.start();
}
}