CAS介绍
CAS - Compare And Swap (Compare And Set, Check And Set)
wikipedia的描述如下:
比较并交换(compare and swap, CAS),是原子操作的一种,可用于在多线程编程中实现不被打断的数据交换操作,从而避免多线程同时改写某一数据时由于执行顺序不确定性以及中断的不可预知性产生的数据不一致问题。 该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值。
Java在sun.misc.Unsafe
类库里面的CAS实现。
以下源码摘自java.util.concurrent.locks.AbstractQueuedSynchronizer
。
/**
* Atomically sets synchronization state to the given updated
* value if the current state value equals the expected value.
* This operation has memory semantics of a {@code volatile} read
* and write.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that the actual
* value was not equal to the expected value.
*/
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
//this: 当前对象
//stateOffSet: 偏移量,声明在下面已贴出
//expect: 期待值
//update: 更新值
//如果stateOffSet的值与expect相等,则将stateOffset的值更新为update;并返回true。
//否则不更新,并返回false。
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
......
private static final long stateOffset;
static {
try {
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
......
} catch (Exception ex) { throw new Error(ex); }
}
CAS与同步方式
在多线程情况下,可以采用同步阻塞(悲观锁)或CAS(乐观锁)的方式实现业务,具体要看业务场景,如果重试的代价很小,那用CAS是合适的,但如果每次重试都需要花费大量的时间或资源,那应该采用同步方式。
以下是2种方式的简单举例:
class MyLock {
private boolean locked = false;
public synchronized boolean lock() {
if(!locked) {
locked = true;
return true;
}
return false;
}
}
public static class MyLock {
private AtomicBoolean locked = new AtomicBoolean(false);
public boolean lock() {
return locked.compareAndSet(false, true);
}
}
CAS缺点 - ABA问题
进程P1读取了一个数值A
P1被挂起(时间片耗尽、中断等),进程P2开始执行
P2修改数值A为数值B,然后又修改回A
P1被唤醒,比较后发现数值A没有变化,程序继续执行。
解决思路:在每次更新的同时附上版本号,如:1A -> 2B -> 3A
。JDK1.5开始新增的java.util.concurrent.atomic.AtomicStampedReference
就是一种实现方式。
Redis中的CAS
Redis可以使用WATCH
来实现对事务中键(可以是多个键)的监视,如果至少有一个键在EXEC
执行前被改动,那么整个事务都会被取消, EXEC
返回nil-reply
来表示事务已经失败。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。