`
rxin2009
  • 浏览: 17368 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

原子类

 
阅读更多

     本文主要介绍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;
		}
		
	}
	
}

 在有些情况下,可以将多个变量封装在一个不可变类中实现线程安全。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics