本文主要介绍jdk中的原子类、ABA问题以及多个变量之间的安全访问。
原子类中核心的一个语法就是CAS操作,而这个操作封装在Unsafe类中,典型的应用如下代码
// setup to use Unsafe.compareAndSwapInt for updates
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类的部分代码,其中valueOffset的值很关键,cas操作都要依赖它的,看下cas操作的代码
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
它有四个参数,this表示实例,valueOffset可看做是value的地址,expect表示期望值,update表示更新值,要表达的意思就是valueOffset处的值等于expect就用update更新,这个操作是在硬件级别上实现的。
典型的应用如下
/**
* 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 decrements by one the current value.
*
* @return the previous value
*/
public final int getAndDecrement() {
for (;;) {
int current = get();
int next = current - 1;
if (compareAndSet(current, next))
return current;
}
}
这种应用很普遍,它是实现非阻塞算法的基础,但是它也有一个特用的问题,叫ABA问题,这个后面在说。
AtomicReference类也是一个常用类,这是针对所有类的一个原子操作的实现,原理和AtomicInteger类似;JDK中也提供了原子的域更新器,可以更新指定类的指定域名,但是原理还是如AtomicInteger类的cas一样,在ConcurrentLinkedQueue中有典型的应用。
现在来说说ABA问题,非阻塞算法的思路是先get一个变量的值,然后执行cas操作,如果失败重复以上的两步操作,成功就返回,问题就在get操作和cas操作之间,一个线程执行了get后,由于线程的交替,另一个线程改变了执行环境,第一个线程执行cas的时候会得到错误的结果。这种问题在链表的操作中比较典型,想具体了解请看这边文章
http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html ,(有图有描述)
在贴一段代码
public class ABATest {
private static AtomicInteger atomicInt = new AtomicInteger(100);
private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<Integer>(100, 0);
public static void main(String[] args) throws InterruptedException {
// testAtomicInteger();
testAtomicStampedReference();
}
// 出现ABA问题
public static void testAtomicInteger(){
Thread intT1 = new Thread(new Runnable() {
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicInt.compareAndSet(100, 101);
atomicInt.compareAndSet(101, 100);
System.out.println("intT1 over");
}
});
// 在线程intT2获得oldValue,执行cas之前的时间段,线程intT1修改atomicInt两次,但intT2的cas操作还是成功执行了
Thread intT2 = new Thread(new Runnable() {
public void run() {
int oldValue=atomicInt.get();
System.out.println("intT2 start");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
}
boolean c3 = atomicInt.compareAndSet(oldValue, 101);
System.out.println(c3); // true
}
});
intT1.start();
intT2.start();
}
// 可以有效避免ABA问题
public static void testAtomicStampedReference(){
Thread refT1 = new Thread(new Runnable() {
public void run(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
System.out.println("refT1 over");
}
});
// cas操作之前atomicStampedRef被修改,那么cas操作将失败
Thread refT2 = new Thread(new Runnable() {
public void run() {
int stamp = atomicStampedRef.getStamp();
System.out.println(stamp);
System.out.println("refT2 start");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
}
System.out.println(atomicStampedRef.getStamp());
boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
System.out.println(c3); // false
}
});
refT1.start();
refT2.start();
}
}
这段代码中提到的ABA问题只是演示,它不会出现错误的结果。
在实现线程安全时,不可变类是一个很重要的概念,一个不可变的类的要求:对象的状态不可修改,对象的域都是final的,对象被正确的构造(不会发生this引用溢出)。多线程访问不可变对象一定是线程安全的,这也常应用于多个变量安全性保证上。如下代码保证两个变量的不变性约束
public class CasNumberRange {
// 将多个变量封装到一个对象中去
private class IntPair{
final int lower;
final int upper;
public IntPair(int lower, int upper){
this.lower=lower; // 不变约束:lower <= upper
this.upper=upper;
}
}
private final AtomicReference<IntPair> values=new AtomicReference<IntPair>(new IntPair(0,0));
public int getLower(){
return values.get().lower;
}
public int getUpper(){
return values.get().upper;
}
public void setLower(int i){
while(true){
IntPair oldValue=values.get();
if(i>oldValue.upper){
throw new IllegalArgumentException("Can't set lower to "+i+" > upper");
}
IntPair newValue=new IntPair(i,oldValue.upper);
if(values.compareAndSet(oldValue, newValue))
return;
}
}
public void setUpper(int i){
while(true){
IntPair oldValue=values.get();
if(i<oldValue.lower){
throw new IllegalArgumentException("Can't set upper to "+i+" < lower");
}
IntPair newValue=new IntPair(oldValue.lower,i);
if(values.compareAndSet(oldValue, newValue))
return;
}
}
}
在有些情况下,可以将多个变量封装在一个不可变类中实现线程安全。