不同于synchronized这种悲观的阻塞同步机制,java.util.concurrent包中还借助CAS实现了乐观的非阻塞同步机制。
CAS(CompareAndSwap):比较并交换
CAS有包含三个操作数:内存值V,旧的预期值A以及更新值B。当且仅当V的值等于A的时候,CAS才通过原子方式用更新值B来更新V的值。否则不做任何操作。
CAS的含义是:“我认为V的值应该是A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少”。
以两个线程都执行i++为例,看看CAS的作用:
我们都知道volatile能保证变量的可见性,但不能保证变量操作的原子性。因此我们就算volatile int i = 0,两个线程都进行i++操作,也不能保证i最后的值是2。
i++这个操作其实包括了读-改-写三个操作,因此可能两个线程都读取了i = 0,最终写回到主存的i就是1而不是2。
而如果用上CAS的话,就可以避免这个问题(下面用三个操作数V,A,B进行说明),
比如线程1读到i的内存值V1 = 0,线程2同样读到V2= 0.然后线程1读取i的预期值A1=0,并计算出更新值B1 = 1进行尝试更新。此时V1 == A1 ,所以V1 = B1,将i的内存值更新成1;然后线程2读取i的预期值A2 = 1。此时V2 != A2,所以不进行任何操作。并再次循环重新读取V,A,B。这就是CAS自旋。
原子变量类的AtomicInteger的getAndIncrement方法(相当于i++)就是基于这样的CAS自旋。
/** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { for (;;) { int current = get(); //预期值 int next = current + 1; //更新值 if (compareAndSet(current, next)) return current; } }
compareAndSet方法:
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }unsafe.compareAndSwapint是一个native方法。所以再用一段代码来模拟下CAS下的i++。
package com.bckj.Thread; /** * 模拟CAS算法 */ public class TestCompareAndSwap { public static void main(String[] args) throws InterruptedException { final SimulatedCAS cas = new SimulatedCAS(); for(int i = 0 ; i< 10;i++) { new Thread(new Runnable() { @Override public void run() { for(;;) { int expectedValue = cas.get(); int newValue = expectedValue+1; System.out.print(""); //增加线程被抢占的几率 if (cas.compareAndSet(expectedValue, newValue)) { System.out.println(Thread.currentThread().getName()+"执行value++成功!"); break; } else System.out.println(Thread.currentThread().getName()+"执行value++的时候,有其他程序修改过value,CAS自旋重新读取内存值"); } } }).start(); } Thread.sleep(1000); System.out.println("最终结果:"+cas.get()); } } class SimulatedCAS{ private volatile int value; public synchronized int get(){ return value; } public synchronized int compareAndSwap(int expectedValue,int newValue){ int oldValue = this.value; if(oldValue == expectedValue){ this.value = newValue; } return oldValue; } public synchronized boolean compareAndSet(int expectedValue,int newValue){ return expectedValue == compareAndSwap(expectedValue,newValue); } }执行结果:
java.util.concurrent包中除了原子变量类,还有AQS(AbstractQueuedSynchronizer)等也使用了CAS。
比如AQS的enq方法等。
private Node enq(final Node node) { for (;;) { //CAS"自旋",直到成功加入队尾 Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
可以用CAS在无锁的情况下实现原子操作,但要明确应用场合,非常简单的操作且又不想引入锁可以考虑使用CAS操作,当想要非阻塞地完成某一操作也可以考虑CAS。不推荐在复杂操作中引入CAS,会使程序可读性变差,且难以测试,同时会出现ABA问题。(ABA问题大致是指内存值V从A变成B,再从B变成A,这时候CAS会认为没有发生变化。解决ABA问题可以考虑增加一个修改计数,只有修改计数不变的且V值不变的情况下才做操作,也可以考虑引入版本号,当版本号相同时才做操作等)