CAS操作
在java中可以通过锁和CAS操作来实现操作的原子性。cas即compare and set的缩写。JVM中的CAS操作利用了处理器提供的CMPXCHG指令实现。自旋CAS实现的基本思路就是循环进行CAS操作直到成功为止。下面的代码展示的是如何通过CAS算法来实现线程同步:
package chapter02;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class CAS {
//使用java concurrent包中的AtomicInteger类来实现CAS操作
private AtomicInteger atomic = new AtomicInteger(0);
//非线程安全的变量i
private int i = 0;
public static void main(String args[]) {
final CAS cas = new CAS();
List<Thread> ts = new ArrayList<Thread>(600);
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for(int j=0;j<10000;j++){
cas.count();
cas.safeCount();
}
}
});
ts.add(t);
}
for(Thread t :ts){
t.start();
}
for(Thread t:ts){
try{
t.join();
}catch(Exception e){
e.printStackTrace();
}
}
System.out.println("unsafe"+cas.i);
System.out.println("safe"+cas.atomic.get());
System.out.println(System.currentTimeMillis()-start);
}
//线程安全的Count方法
private void safeCount() {
for (;;) {
//atomic的get()方法获取volatile变量value
int i = atomic.get();
//如果设置成功,则获得运行权,否则所有的线程都通过自旋的方法获取执行权
boolean suc = atomic.compareAndSet(i, ++i);
if (suc) {
break;
}
}
}
//非线程安全的count方法
private void count() {
i++;
}
}
上面的代码运行结构为:
unsafe834081
safe1000000
87
CAS原理:
通过查看AtomicInteger的源码可知,
`private volatile int value;
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}`
通过申明一个volatile (内存锁定,同一时刻只有一个线程可以修改内存值)类型的变量,再加上unsafe.compareAndSwapInt的方法,来保证实现线程同步的。
缺点:
1、ABA问题
CAS操作容易导致ABA问题,也就是在做a++之间,a可能被多个线程修改过了,只不过回到了最初的值,这时CAS会认为a的值没有变。a在外面逛了一圈回来,你能保证它没有做任何坏事,不能!!也许它讨闲,把b的值减了一下,把c的值加了一下等等,更有甚者如果a是一个对象,这个对象有可能是新创建出来的,a是一个引用呢情况又如何,所以这里面还是存在着很多问题的,解决ABA问题的方法有很多,可以考虑增加一个修改计数,只有修改计数不变的且a值不变的情况下才做a++,也可以考虑引入版本号,当版本号相同时才做a++操作等,这和事务原子性处理有点类似!
2、比较花费CPU资源,即使没有任何争用也会做一些无用功。
3、会增加程序测试的复杂度,稍不注意就会出现问题。
总结:
可以用CAS在无锁的情况下实现原子操作,但要明确应用场合,非常简单的操作且又不想引入锁可以考虑使用CAS操作,当想要非阻塞地完成某一操作也可以考虑CAS。不推荐在复杂操作中引入CAS,会使程序可读性变差,且难以测试,同时会出现ABA问题。