Java的多线程机制系列:(二)缓存一致性和CAS(二)
前的值一致为止,才执行更新。
以concurrent中的AtomicInteger的代码为例,其的getAndIncrement()方法(获得并且自增,即i++)源代码如下:
复制代码
/**
* 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;
}
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
复制代码
其调用了compareAndSet(int expect,int update)方法,其中expect是期望值,即操作前的原始值,而update是操作后的值,以i=2为例,则这里的expect=2,update=3,它调用了sun.misc.Unsafe的compareAndSwapInt方法来执行,此方法代码如下:
复制代码
/***
* Compares the value of the integer field at the specified offset
* in the supplied object with the given expected value, and updates
* it if they match. The operation of this method should be atomic,
* thus providing an uninterruptible way of updating an integer field.
*
* @param obj the object containing the field to modify.
* @param offset the offset of the integer field within
obj.
* @param expect the expected value of the field.
* @param update the new value of the field if it equals
expect.
* @return true if the field was changed.
*/
public native boolean compareAndSwapInt(Object obj, long offset,
int expect, int update);
复制代码
这是一个本地方法,即利用CAS保证其原子性,同时如果失败了则通过循环不断地进行运算直到成功为止,这是和JDK5以前最大的区别,失败的线程不再需要被挂起、重新调度,而是可以无障碍地再度执行,这又极大减少了挂起调度的开销(当然如果CAS长时间不成功,也会造成耗费CPU,这取决于具体应用场景)。
CAS策略有如下需要注意的事项:
在线程抢占资源特别频繁的时候(相对于CPU执行效率而言),会造成长时间的自旋,耗费CPU性能。
有ABA问题(即在更新前的值是A,但在操作过程中被其他线程更新为B,又更新为A),这时当前线程认为是可以执行的,其实是发生了不一致现象,如果这种不一致对程序有影响(真正有这种影响的场景很少,除非是在变量操作过程中以此变量为标识位做一些其他的事,比如初始化配置),则需要使用AtomicStampedReference(除了对更新前的原值进行比较,也需要用更新前的stamp标志位来进行比较)。
只能对一个变量进行原子性操作。如果需要把多个变量作为一个整体来做原子性操作,则应该使用AtomicReference来把这些变量放在一个对象里,针对这个对象做原子性操作。
CAS在JDK5中被J.U.C包广泛使用,在JDK6中被应用到synchronized的JVM实现中,因此在JDK5中J.U.C的效率是比synchronized高不少的,而到了JDK6,两者效率相差无几,而synchronized使用更简单、更不容易出错,所以其是专家组推荐的首选,除非需要用到J.U.C的特殊功能(如阻塞一段时间后放弃,而不是继续等待)。