《Java并发编程实践》第15章 原子变量与非阻塞同步机制 读书笔记
Java中的对共享变量访问的同步机制
- synchronized内部锁
- 显示锁
- volatile
- 原子变量
volatile变量 VS 锁
- volatile变量更轻量级,因为它们不会引起上下文的切换和线程的调度。对于锁,当频繁地发生锁的竞争时,上下文切换和调度开销可能远大于工作开销。
- 都提供了可见性保证。
- volatile变量不能用于构建原子化的复合操作,例如i++。
- 加锁的缺点。当一个线程正在等待锁时,它不能做任何事情。如果另一个线程在持有锁的情况下发生延迟(原因包括页错误、调度延迟等),那么其他所有需要该锁的线程都不能前进了。
原子变量类(Atomic Variable Classes)
于是,就需要类似于volatile变量的机制,并且还要支持原子化更新的技术。原子变量类就满足了这样的需求。
原子变量类共12个,分4组
- 计数器
AtomicInteger AtomicLong AtomicBoolean AtomicReference
- 域更新器(field updater)
AtomicIntegerFieldUpdater AtomicLongFieldUpdater AtomicReferenceFieldUpdater
- 数组
AtomicIntegerArray AtomicLongArray AtomicReferenceArray
- 复合变量
AtomicMarkableReference AtomicStampedReference
Atomic类的实现依赖于冲突监测,从而能判定更新过程中是否存在来自于其他成员的干涉,在冲突发生的情况下,操作失败,并会重试(也可能不重试)。
现代的处理器提供了原子化的读-改-写指令,如比较并交换(compare-and-swap)。CAS有3个操作数,内存位置V,旧的预期值A,新值B。当且仅当V等于旧的预期值A时,CAS用新值B原子化地更新V的值,否则什么都不会做。在任何一种情况下,都会返回V的真实值。
若处理器不支持这样的指令,JVM会使用自旋锁。
利用AtomicInteger实现i++复合操作。
AtomicInteger i = new AtomicInteger(0);
i.incrementAndGet();
相关Java源码
package java.util.concurrent.atomic;
import sun.misc.Unsafe;
public class AtomicInteger extends Number implements java.io.Serializable {
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
incrementAndGet方法体内是一个for循环,表示如果冲突发生,就不断重试,直到compareAndSet方法返回true。
compareAndSet方法调用的是Unsafe类的compareAndSwapInt方法,该方法是一个native方法。
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
该native方法的最终实现源码路径为openjdk\hotspot\src\os_cpu\windows_x86\vm\atomic_windows_x86.inline.hpp
// Adding a lock prefix to an instruction on MP machine
// VC++ doesn't like the lock prefix to be on a single line
// so we can't insert a label after the lock prefix.
// By emitting a lock prefix, we can define a label after it.
#define LOCK_IF_MP(mp) __asm cmp mp, 0 \
__asm je L0 \
__asm _emit 0xF0 \
__asm L0:
inline jint Atomic::cmpxchg(jint exchange_value, volatile jint* dest, jint compare_value) {
// alternative for InterlockedCompareExchange
int mp = os::is_MP();
__asm {
mov edx, dest
mov ecx, exchange_value
mov eax, compare_value
LOCK_IF_MP(mp)
cmpxchg dword ptr [edx], ecx
}
}
如源代码所示,如果是多处理器机器,LOCK_IF_MP(mp)就会为cmpxchg指令加上lock前缀。原因是有可能多个处理器同时从各自的缓存中读取共享变量,分别进行操作,然后分别写入系统内存当中。这时就需要加锁,其中一种方式就是总线锁。
ABA问题
CAS只检测”V的值是否仍为A“,然而可能V的变化过程是A -> B -> A,这就是ABA问题。要知道“V的值在我上次观察后是否发生变化”,一种解决方案是,更新一对值,包括引用和版本号。A改为B,又改回A,版本号不同。AtomicStampedReference或AtomicMarkableReference提供了一对变量原子化的条件更新。
AtomicStampedReference更新对象引用的整数对
AtomicMarkableReference更新对象引用的布尔对