一、 竞态条件(Race Condition)
在多线程的环境下,程序输出的结果会受到线程执行顺序的影响的现象叫做竞态条件。举个例子:
class Counter {
private int count = 0;
/**
* @return the count
*/
public int getCount() {
return count;
}
public void increment() {
count++;
}
}
上述代码实现了一个计数器。在串行执行的代码里这个计数器可以正常地工作。然而到了多线程的环境下可就不一定了。因为虽然增量操作(count++)看上去类似一个单独操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,在执行任意一步的时候都有可能发生线程切换。
二、 原子操作(atomic operation)
原子操作是指操作一旦开始就会执行到结束,而不会被线程切换而中断,即要么全部执行,要么不开始。原子操作是线程安全的,而且比锁更加严谨。它不会产生死锁,也不会因为一些没有遵循加锁机制的代码而被破话。
Java的java.util.concurrent.atomic包下面提供了大量的支持原子操作的类,比如说AtomicInteger。它提供的getAndIncrement()的方法就可以很好地解决上面的代码在多线程的环境下所遇到的问题:
class Counter {
private AtomicInteger count = new AtomicInteger(0);
/**
* @return the count
*/
public int getCount() {
return count.get();
}
public void increment() {
count.getAndIncrement();
}
}
getAndIncrement是原子的,所以操作执行过程中就不会再出现线程切换的问题了。
三、CAS (Compare and Swap)
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。循环的CAS直到它可以被完成是Java实现原子性的方式。再底层是通过JNI调用本地的C++代码,最终是由CPU的CMPXCHG指令提供的。下面是incrementAndGet的实现:
1. incrementAndGet
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
2. compareAndSet
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
3. compareAndSwapInt
/**
* Atomically update Java variable to <tt>x</tt> if it is currently
* holding <tt>expected</tt>.
* @return <tt>true</tt> if successful
*/
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
4. 本地C++代码
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
需要注意到的是:
1. 并非所有的CPU都支持CAS指令,有些架构的CPU的CAS实现本质就是加锁;
2. CAS会带来所谓“ABA”问题;虽然这个一般情况下都不是问题~
3. 如果在线程抢占资源特别频繁的时候,长时间的CAS自旋则会带来性能的下降;自适应的自旋等待可以很好的解决这个问题
4. 当不变性条件中涉及多个变量时,单单使用原子变量时无法保证线程安全的。