比较交换是一种无锁的并发策略,使用这种方式可以避免锁竞争带来的系统开销,以及线程间频繁调度带来的系统开销,因此具有比锁更好的性能。CAS算法如下:它包含3个参数CAS(V,E,N),V代表要更新的变量,E代表预期值,N代表新值。仅当V值等于E时,才会将V设置为N,如果V不等于N,说明有其他线程对V进行了操作,则当前线程什么也不做。因此每一次只有一个线程会操作成功,其余的均会失败,失败的线程并不会被挂起,只是被告知失败,并会继续尝试,直到操作成功。
Java并发包中提供了一个atomic包,里面实现了一些CAS操作的线程安全的类型。
1.无锁的线程安全整数(AtomicInteger)内部实现分析(基于jdk1.8):
private volatile int value;
value代表AtomicInteger的值,用volatile修饰,保证了线程间的可见性
private static final long valueOffset;
valueOffset保存了value字段在AtomicInteger对象上的偏移量,通过valueOffset可以定位到AtomicInteger对象value字段的位置,其实现如下:
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
常用方法举例:
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//当前值加1,并返回加1前的值
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
//当前值加delta,并返回先前的值
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
//当前值减1,并返回先前的值
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
当前值加delta,并返回加delta之前的值
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
如果当前值与预期值(expect)相等,则将当前值更新为新值(update)
public float floatValue() {
return (float)get();
}
将整型转化为float型
该类中的方法基本都调用unsafe实现,而unsafe中的方法底层采用c实现,比如:
public native boolean compareAndSwapInt(Object obj, long offset,int expect,int update);
类似的实现还有AtomicLong、AtomicBoolean、AtomicReference,此处不再赘述
2.存在的问题:
使用CAS操作无法判断当前数据的状态,当某个线程修改了value,下一个线程又把它修改回上一个线程修改前的值,那么之后线程并不知道它曾被修改过。即常说的ABA问题。
针对这种情况,JDK提供了AtomicStampedReference类,其内部同时维护对象值和一个时间戳。当AtomicStampedReference对象的值被修改时,除了要更新对象值之外,还要同时更新时间戳。当AtomicStampedReference设置对象值时,对象值和时间戳都要满足期望值,写入才会成功。因此即使对象值被写会原值,只要时间戳发生变化,就能防止不恰当的写入。
内部实现分析:
private volatile Pair<V> pair;
pair保存了对象值以及时间戳,Pair类源码如下:
private static class Pair<T> {
final T reference; //对象值
final int stamp; //用一个整数表示状态值,即时间戳
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
以 compareAndSet方法为例:
/**
* Atomically sets the value of both the reference and stamp
* to the given update values if the
* current reference is {@code ==} to the expected reference
* and the current stamp is equal to the expected stamp.
*
* @param expectedReference the expected value of the reference
* @param newReference the new value for the reference
* @param expectedStamp the expected value of the stamp
* @param newStamp the new value for the stamp
* @return {@code true} if successful
*/
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
当期望值与当前值相等,且期望时间戳和当前时间戳相等时,才会写入新值,并同时更新时间戳,casPair方法实现如下:
private boolean casPair(Pair<V> cmp, Pair<V> val) {
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
3.不安全类(unsafe)
Java不能直接访问操作系统底层,而是通过本地方法来访问,其中封装了一些类似指针的操作,使用unsafe类可以分配内存,释放内存,类中提供3个本地方法allocateMemory、reallocateMemory、freeMemory分别用于分配内存,扩充内存和释放内存
unsafe主要方法如下:
//获得给定对象偏移量上的int值
private native int getInt(Object o,long offset);
//设置给定对象偏移量上的int值
private native int putInt(Object o,long offset,int x);
//获得字段在对象上的偏移量
private native long objectFieldOffset(Field f);
//设置给定对象的int值,使用volatile语义
private native void putIntVolatile(Object o,long offset,int x);
//获得的给定对象的int值,使用volatile语义
private native int getIntVolatile(Object o,long offset);
//和putIntVolatile一样,不过它要求被操作字段就是volatile类型的
private native void putOrderedInt(Object o,long offset,int x);
JDK开发人员并不希望大家使用这个类,获得unsafe实例的方法是调用其工厂方法getUnsafe(),其实现如下:
public static Unsafe(){
Class cc=Reflection.getCallerClass();
if(cc.getClassLoader()!=null)
throw new SecurityException("Unsafe");
return theUnsafe;
}
调用该方法时,它会检查调用该方法的类,如果这个类的ClassLoader不为null,就直接抛出异常,拒绝访问。