CAS算法概述
CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。
CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。
注:t1,t2线程是同时更新同一变量56的值
因为t1和t2线程都同时去访问同一变量56,所以他们会把住内存的值完全拷贝一份到自己的工作内存空间,所以t1和t2线程值都为56
假设t1和t2在线程竞争中线程t1能去更新变量值改为57,而其他线程都失败。(失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次发起尝试)。T1线程去更新变量值改为57,然后写到内存中。此时对于t2来说,内存值改为57,与预期值56不一致,就操作失败了(想改的值不再是原来的值)。
CAS算法的开销主要来自Cache Miss,一旦导致CAS一直更新失败的话,它的性能是有可能坏于加锁的方式的。
ABA问题概述
上面我我们说了CAS算法,CAS实现的过程是先取出内存中某时刻的数据,在下一时刻比较并替换,那么在这个时间差会导致数据的变化,此时就会导致出现“ABA”问题。关于“ABA”问题,我们假设如下事件序列:
线程 1 从内存位置V中取出A。
线程 2 从位置V中取出A。
线程 2 进行了一些操作,将B写入位置V。
线程 2 将A再次写入位置V。
线程 1 进行CAS操作,发现位置V中仍然是A,操作成功。
尽管线程 1 的CAS操作成功,但不代表这个过程没有问题——对于线程 1 ,线程 2 的修改已经丢失。
我们形象地画一个图来打个比方:
解决方法
我们在AtomicReference的使用中就遇到了这样的ABA问题,name怎么解决的呢?我们使用AtomicStampedReference就能很好的解决这个问题了,首先,我们先看一下这一段代码(实现自动充值,当少于20元时,充值20元,再进行消费,每次消费10元):
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicReferenceDemo { static AtomicStampedReference<Integer> money = new AtomicStampedReference<Integer>(19, 1); static Object obj = new Object(); public static void main(String[] args) { Add add = new Add(); Thread thread1 = new Thread(add); thread1.start(); Reduce reduce = new Reduce(); Thread thread2 = new Thread(reduce); thread2.start(); } static class Add implements Runnable { @Override public void run() {while (true) { Integer m = money.getReference(); synchronized (obj) { // 1处 if (m < 20) { if (money.compareAndSet(m, m + 20, money.getStamp(), money.getStamp() + 1)) { //2处 System.out.println("充值成功,余额:" + money.getReference() + "元"); break; } } else { break; } } } } } static class Reduce implements Runnable { @Override public void run() { while (true) { while (true) { Integer m = money.getReference(); synchronized (obj) { //3处 if (m > 10) { if (money.compareAndSet(m, m - 10, money.getStamp(), money.getStamp() + 1)) { //4处 System.out.println("成功消费10元,余额:" + money.getReference() + "元"); break; } } else { break; } } } try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
我们在1处2处加上锁之后就OK了!这是结果:
这样问题就很好的解决了,不过特别注意的是在1处和3处需要加锁,因为2处和4处的if条件是一个原子操作,大家都知道,java是抢占式的,线程可能在这个原子操作执行结束后被另一个线程所抢占,这样就是导致打印的时候的值不准确。