简单的说,CAS 需要你额外给出一个期望值,也就是你认为这个变量现在应该是什么样子的。如果变量不是你想象的那样,哪说明它已经被别人修改过了。你就需要重新读取,再次尝试修改就好了。
这样归功于硬件指令集的发展,实际上,我们可以使用同步将这两个操作变成原子的,但是这么做就没有意义了。所以我们只能靠硬件来完成,硬件保证一个从语义上看起来需要多次操作的行为只通过一条处理器指令就能完成。这类指令常用的有:
JMM详细讲解过
public class Cas1 {
private static volatile int m = 0;
private static AtomicInteger atomic = new AtomicInteger(0);
public static void increase1() {
m++;
}
public static void increase2() {
atomic.incrementAndGet();
}
public static void main(String[] args) throws InterruptedException {
Thread[] t = new Thread[20];
for (int i = 0; i < 20; i++) {
t[i] = new Thread(() -> {
Cas1.increase1();
});
t[i].start();
t[i].join();
}
System.out.println("m:" + m);
Thread[] tf = new Thread[20];
for (int i = 0; i < 20; i++) {
tf[i] = new Thread(() -> {
Cas1.increase2();
});
tf[i].start();
tf[i].join();
}
System.out.println("atomic:" + atomic.get());
}
}
JUC下的atomic类都是通过CAS来实现的,下面就以AtomicInteger为例来阐述CAS的实现。如下java -p 查看指令集:
查看AtomicInteger
//Unsafe 后门类,操作内存数据
private static final Unsafe unsafe = Unsafe.getUnsafe();
//内存地址偏移量
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//要修改的值
private volatile int value;
看看AtomicInteger如何实现并发下的累加操作
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
//unsafe.getAndAddInt
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
假设线程A和线程B同时执行getAndAdd操作(分别跑在不同CPU上):
继续深入看看Unsafe类中的compareAndSwapInt方法实现
public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);
Unsafe类中的compareAndSwapInt,是一个本地方法,该方法的实现位于unsafe.cpp中
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
如果是Linux的x86,Atomic::cmpxchg方法的实现如下:
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os::is_MP();
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
Window的x86实现如下:
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os::isMP(); //判断是否是多处理器
_asm {
mov edx, dest
mov ecx, exchange_value
mov eax, compare_value
LOCK_IF_MP(mp)
cmpxchg dword ptr [edx], ecx
}
}
// 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:
LOCK_IF_MP根据当前系统是否为多核处理器决定是否为cmpxchg指令添加lock前缀(前面写过)。
CAS虽然高效地解决了原子操作,但是还是存在一些缺陷的,主要表现在三个方法:循环时间太长、只能保证一个共享变量原子操作、ABA问题。
加入版本
public class Cas3 {
private static AtomicStampedReference atomic = new AtomicStampedReference(100,1);
public static void main(String[] args) {
Thread t1=new Thread(()->{
System.out.println(atomic.compareAndSet(100,110,atomic.getStamp(),atomic.getStamp()+1));
System.out.println(atomic.compareAndSet(110,100,atomic.getStamp(),atomic.getStamp()+1));
});
Thread t2=new Thread(()->{
int stamp = atomic.getStamp();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomic.compareAndSet(110,120,stamp,stamp+1));
});
t1.start();
t2.start();
}
}