本文主要介绍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; } } }
在有些情况下,可以将多个变量封装在一个不可变类中实现线程安全。