CAS的理解
什么是CAS
CompareAndSweep比较并交换。
AtomicInteger atomicInteger = new AtomicInteger(1);
//期望值是1,如果达到,那么就更新为2,否则不更新
atomicInteger.compareAndSet(1,2);
自旋锁
比较当前线程工作内存中的值和主内存中的值,如果这个值是期望的,那么就执行操作,否则一直循环
缺点:
- 循环会耗时。
- 一次性只能保证一个共享变量的原子性。
- 存在ABA问题。
ABA问题
前面可能出现ABA问题,什么是ABA问题?
简单来说,就是当线程A在操作某个值时,线程B将主存中的值改为某值后又还原,线程A不知道这个值已经被人修改过。
如何解决?
通过原子引用
也就是一个带版本号的类,类似于数据库的乐观锁。
//这里设置值的时候传一个版本号
AtomicStampedReference<Integer> asr= new AtomicStampedReference<>(1,1);
//这里操作这个值的时候就将版本号加1,返回值为true则说明这个值未被别的线程修改过,false反之。
asr.compareAndSet(1,2, asr.getStamp(),asr.getStamp()+1);
Volatile
Volatile是java虚拟机提供的轻量级的同步机制
1.保证可见性
2.不保证原子性
3.禁止指令重排
可见性体会
public class JMMDemo {
//保证可见性,如果不加,线程1会死循环
private volatile static int num = 0;
public static void main(String[] args) {
new Thread(()->{
//线程1
int count = 0;
while(num == 0){
}
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch(Exception e){
e.printStackTrace();
}
num = 1;
System.out.println(Thread.currentThread().getName()+num);
}
}
不保证原子性体会
//不保证原子性
public class VDemo02 {
private volatile static int num = 0;
public static void add(){
num++;//不是一个原子性操作
}
public static void main(String[] args) {
//理论num结果应该为2万
for (int i = 1; i <= 20; i++) {
new Thread(() ->{
for(int j = 0; j < 1000; j++){
add();
}
},String.valueOf(i)).start();
}
while(Thread.activeCount() > 2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
}
- 如上操作,num++不是一个原子操作,多个线程共同操作这个方式,会导致num的值不为预期值20000。
- 要解决这个问题,可以使用原子类,也就是上面使用到的AtomicInteger来修饰num。
禁止指令重排
我们写的程序,计算机并不是按照我们写的那样执行。
源代码 --> 编译器优化的重排–>指令并行也可能会重排–>内存系统也会重排–>执行
处理器进行指令重排,会考虑数据之间的依赖性。
而被Volatile修饰的变量在写操作时,会生成内存屏障,保证操作的执行顺序,指令不会被重排。