CAS
CAS概念:
CAS号称是无锁优化,或者叫自旋。
我们先通过一个简单的例子来说明为什么要使用CAS。
public class T { private int count = 10; public void m() { count--; System.out.println(Thread.currentThread().getName() + " count = " + count); } public static void main(String[] args) { T t = new T(); for (int i = 0; i < 10 ; i++) { new Thread(()->t.m()).start(); } } }
这段代码的执行结果如下:
可以看到结果并不是我们想要的,我们可以通过加锁的方式去实现:
public class T { private int count = 10; public synchronized void m() { count--; System.out.println(Thread.currentThread().getName() + " count = " + count); } public static void main(String[] args) { T t = new T(); for (int i = 0; i < 10 ; i++) { new Thread(()->t.m()).start(); } } }
结果如下:
虽然我们实现了同步,但是加锁对系统资源的消耗是非常大的,所以java为我们提供了一些通过CAS实现原子变量类,
比如我们来看如下代码:
import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class T2 { private int count = 0; public void m() { count++; } public static void test(){ T2 t = new T2(); List<Thread> threadList = new ArrayList<>(); for (int i = 0; i < 55000 ; i++) { threadList.add(new Thread(()->t.m())); } threadList.forEach(thread -> thread.start()); threadList.forEach(thread -> { try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println(t.count); } public static void main(String[] args) { for (int i = 0; i <10 ; i++) { test(); } } }
执行结果如下:
可以看到,我们通过多线程去操作一个共享变量,得到得结果并不是准确的,也就是说存在线程安全问题。当然我们可以用synchronized关键字来同步,但是比较笨重。
这里我们可以使用Atomic包下的AtomicInteger来实现同步,具体代码如下:
import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class T1 { private AtomicInteger count = new AtomicInteger(0); public void m() { count.incrementAndGet(); } public static void test(){ T1 t = new T1(); List<Thread> threadList = new ArrayList<>(); for (int i = 0; i < 55000 ; i++) { threadList.add(new Thread(()->t.m())); } threadList.forEach(thread -> thread.start()); threadList.forEach(thread -> { try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println(t.count); } public static void main(String[] args) { for (int i = 0; i < 10; i++) { test(); } } }
执行结果如下:
可以看到我们没有加锁,但是同样达到了同步的目的。AtomicInteger的底层就是CAS实现的,那么CAS究竟是怎么实现的呢?
CAS好比一个代理人(中间人),共享同一变量V的线程就是他的客户,当客户需要更新变量V的值时,他们需要请求代理人代为
修改,为此,客户要告诉代理人其看到的共享变量的当前值A以及期望值B。CAS作为代理人,相当于如下伪代码所示的函数:
boolean compareAndSwap(Variable V,Object A,Object B){ //如果线程看到的值和当前值A相等,则说明共享变量V没有被其他线程改变 if(A==V.get()){ V.set(B);//更新变量值 return true;//更新成功 } return false;//更新失败,继续请求代理人代为修改 }
该操作的原子性由处理器保障,所以该操作不存在线程安全问题。
java.util.concurrent.atomic下的类的底层都是通过CAS来保障线程安全的,这里就不一一举例了。很多小伙伴会发现我们在实际
开发中,其实很少用到这些类,基本上大家都是用synchronized加锁。但是在超高并发的情况下,我们就要慎重选择了。但是我们
一定不要武断的认为synchronized效率一定比CAS低,现在synchronized有了锁升级的概念,在不是超高并发的情况下,真的并不
一定效率低于CAS。
ABA问题:
ABA问题其实很简单,我们回过头去看CAS的实现,当当前线程期望的值和共享变量相等的时候,我们就判定共享变量没有被修改,
但是真的是这样吗?其实不是的,如果在这期间共享变量A被一个线程修改为B,然后又被另一个线程修改为A,其实以及发生了改变,只是
又被改回了原来的值,当前线程无法得知而已。这就是ABA问题。
那么如何规避ABA问题呢,也很简单,只需要在更新共享变量的的时候引入一个修订号,每次都增加1。例如A-B-A改为[A,0]-[A,1]-[A-1]。
这样我们就能准确的判断共享变量是否被其他线程修改过。AtomicStampedReference类就是基于这种思想产生的。