JUC包下的原子类是CAS算法运用的典范
1.引入(线程安全的累加器)
我们来看平常的一个累加器
public class AutomicDemo {
private static AtomicInteger count = new AtomicInteger();
public void add() {
for (int i = 0; i < 1000; i++) {
count.addAndGet(1);
}
}
public static AtomicInteger result() throws InterruptedException {
//创建两个线程,执行累加器
AutomicDemo demo = new AutomicDemo();
Thread threadOne = new Thread(() -> {
demo.add();
});
Thread threadTwo = new Thread(() -> {
demo.add();
});
threadOne.start();
threadTwo.start();
threadOne.join();
threadTwo.join();
return count;
}
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
AtomicInteger result = AutomicDemo.result();
System.err.println("最终的运算结果是:" + result);
}
}
以上程序使用了原子类并不会发生线程安全问题。那原子类是怎么保证线程安全的呢?它的实现方式并不是加锁,而是使用了CAS算法(check and swap)。
2.CAS介绍
CAS相对与互斥锁方案,最大的优点就是性能。互斥锁为了达到互斥的目的操作前后都要有加锁解锁。这样性能损耗很大,而CAS采用无锁的方式达到了互斥的效果,性能相对而言较高。
那么原子类中的CAS怎么实现的呢,这也要依靠硬件的支持,CAS指令有三个参数(共享变量的地址A,用于比较的变量B, 更新的新值C),只有当A=B时候,将A处的值更新为C。作为一条CPU指令,CAS指令本身是可以保证原子性的。
3.Java模拟CAS算法
(当然CAS底层不是通过synchronized实现的,这里只是为了便于理解,理解逻辑就行)
class SimpleCAS{
public volatile int count;
/**
* 模拟CAS的更新值过程
* @param exceptValue
* @param newValue
* @return
*/
public synchronized int cas(int exceptValue, int newValue){
//读取当前的值
int currentValue = count;
//如果当前值与预期值相等就赋新值,否则不变
if(currentValue == exceptValue){
count = newValue;
}
return currentValue;
}
/**
* 模拟自旋过程(如果与可预期值不相等,会不断尝试)
* @return
*/
public synchronized void addOne(){
int newValue ;
do{
newValue = count + 1;
}while(count != cas(count, newValue));
}
}
CAS算法会出现的问题
- ABA问题,也就是加入之前的值的变化是 1-》2-》1,最后还是1,那么就会和exceptValue相等,这样会出现问题,解决的方式是增加一个版本号,由于版本号是递增的,每次变化都不一样,每次更新值之前还要检查一下版本号
- 不断自旋