CAS操作3大问题: 1. ABA问题:值原来是A,变成了B,再变成A,那么CAS检查值时没有发生变化,但实际发生了。ABA问题解决思路就是加版本号AtomicStampedReference,这个类的compareAndSet方法的作用是先检查当前引用是否是预期引用,并检查当前标志是否是预期标志。全部相等才会做更新操作。 2.循环时间长开销大。 3.只能保证一个共享变量的原子操作:可以使用共享变量,AtomicReference类可保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。 以下代码说明了多处理器i++(读改写操作)的问题,比较安全计数器和非安全计数器结果即可:
import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** * Created by bai020 on 2018/1/24. * * @author:bai020 * @Description: * @Date:Created in 19:01 on 2018/1/24. */ public class CASTest { private AtomicInteger atomicI = new AtomicInteger(0); private int i1 = 0; /** * i++操作(读改写操作)来解释,如果i=1,进行两次i++操作,期望结果是3,但有可能结果是2, * 原因可能是多个处理器同时从各自的缓存中读取变量i,分别进行加1操作,然后分别写入系统内存中。这样两次i++操作结果是2 * @param args */ public static void main(String[] args) { final CASTest cas = new CASTest(); long start = System.currentTimeMillis(); //创建线程list List<Thread> ts = new ArrayList<>(1000); for (int j = 0; j < 1000; j++) { Thread t = new Thread(() -> { for (int i = 0; i < 10000; i++) { cas.count(); cas.safeCount(); } }); ts.add(t); } //所有线程启动 for (Thread t : ts) { t.start(); } //等待所有线程执行完 for (Thread t : ts) { try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("非线程安全计数器结果:" + cas.i1); System.out.println("CAS线程安全计数器结果:" + cas.atomicI); System.out.println("处理时间:" + (System.currentTimeMillis() - start)); } /** * 线程安全的计数器,原子类通过CAS自旋保证线程安全 * 输出结果为10000000 */ private void safeCount(){ for(;;){ int i = atomicI.get(); boolean suc = atomicI.compareAndSet(i, ++i); if(suc){ break; } } } /** * 非线程安全的计数器 * 结果少于10000000 */ private void count(){ i1++; } }