volatile引起的思考
首先CAS是一种思想和算法,Compare And Swap,即比较并交换,是对volatile的补充和延申,我们知道volatile只能保证可见性和禁止指令重排序,但是不能解决原子操作,最典型的就是a++操作。
在并发情况下,a++会产生线程安全问题,主要是因为java内存模型和线程竞争机制引起,这里回顾一下a++在高并发下的安全问题。所为原子性是指一系列操作不可分割和打断,要么都成功要么都不成功,而a++执行分三个步骤,首先主内存中读取数值到工作内存操作,然后修改数值,第三步是写回主内存,如果是单线程这没什么安全问题,但是多线程同时去修改就会产生问题,比如线程A读取a的值是1还没来得及修改,线程B再读取a的值并修改为2,这时线程A继续工作,这个时候线程A还认为a的值是1,并加1,再将a等于2写入内存中,实际情况是a的值应该是3了,下面是代码演示:
public class S {
volatile static int a;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new VoDemo());
Thread thread1 = new Thread(new VoDemo());
thread.start();
thread1.start();
thread.join();
thread1.join();
System.out.println(a);
}
static class VoDemo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
a++;
}
}
}
}
结果是14767,执行了20000次,明显线程不安全。
这是由于volatile不能保证原子性的原因,而利用CAS且能保证原子性,所谓CAS原理就是认为v的值是A,如果是把v的值变成B,如果不是A,说明有人修改过了,那就不再修改,避免同时多人修改照成错误。
共有三个操作数:内存值V,预期值A、要修改的值B,当且仅仅预期值A等于内存值V、才将内存值改为B,否则什么都不做,最后返回V,最终实现原子性,他是属于乐观锁,认为每次没人会来抢,所以不用加锁,在并法包和原子类用的比较多
下面对代码进行改造
public class S {
//原子整形,利用CAS原理保证原子性
static AtomicInteger a=new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new VoDemo());
Thread thread1 = new Thread(new VoDemo());
thread.start();
thread1.start();
thread.join();
thread1.join();
System.out.println(a);
}
static class VoDemo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
a.incrementAndGet();
}
}
}
}
发现两个线程同时执行共20000次结果为20000
代码演示CAS原理
public class S {
static int expectedValue;
public static void main(String[] args) throws InterruptedException {
compareAndSet(0, 1);
}
private static int compareAndSet(int a, int b) {
//判断预期值
if (expectedValue == a ) {
//如果等于预期值就更新
expectedValue = b;
}else {
//否则返回原来的值
return expectedValue;
}
return expectedValue;
}
}
CAS的问题
同样CAS也会存在风险,那就是ABA问题,所谓ABA就是一个线程将值改为B,另外一个线程又改为A,第三个线程修改的时候发现值还是A,实际上已经有修改过,解决办法就是做一个版本号,每次修改都会在版本号上加1
CAS存在自旋问题,在并发下不断地比较自旋会消耗CPU资源