J.U.C 系列之Atomic原子类

一 什么是原子类?

所谓原子类必然是具有原子性的类,原子性操作--原子操作,百度百科中给的定义如下

"原子操作(atomic operation)是不需要synchronized",这是Java多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切[1]  换到另一个线程)。

顾名思义,原子类就是一个一旦被执行就不能中断的类。

 

二 为什么需要原子类?

在看为什么需要原子类之前,我们看看普通Number类,在处理问题时可能存在的问题,这里我们通过Integer来演示

public class Main {

    private static Integer sum = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(new Task()).start();
        }

        Thread.yield();
        System.out.println(sum);

    }

    static class Task implements Runnable {
        @Override
        public void run() {
            for(int i = 0;i<100;i++){
                sum++;
            }
        }
    }
}

这段代码意思是开20个线程,每个线程对sum自加100次,理论上应该最后输出2000;但是事实上每次都是小于2000;

这是听说过volatile关键字的小伙伴可能会说,使用volatile来修饰sum,好,我们继续试验

public class Main {

    private static volatile Integer sum = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(new Task()).start();
        }

        Thread.yield();
        System.out.println(sum);

    }

    static class Task implements Runnable {
        @Override
        public void run() {
            for(int i = 0;i<100;i++){
                sum++;
            }
        }
    }
}

这是试验五次的输出

1766,1616,1859,1980,1800

还是都是小于2000,这是怎么回事,这里先提一下,volatile只能保证单个操作的原子性,而sum++,包括三个操作

sum = getSum()  //读取sum

temp = sum +1;  //sum+1,赋给临时变量

sum =  setSum(temp)      //将sum写回

因此,即使是volatile也无法保证sum++的原子性,volatile只能保证单个操作的原子性,而++操作是复合操作,volatile变量会在后续章节详细讨论;

那么,在Atomic未出现之前,是如何处理i++在多线程环境下的线程安全问题,主要是通过Synchronize加锁来处理,处理过程复杂,性能低

JDK5.0之后出现的Java.util.concurrent.Atomic包中为我们提供了13中原子类,来保证单个原子变量复合操作的原子性。下面我们通过AtomicInteger的使用来认识一下原子类

 

三 原子类示例详解

AtomicInteger 字段

   // setup to use Unsafe.compareAndSwapInt for updates
   //这里, unsafe是java提供的获得对对象内存地址访问的类,注释已经清楚的写出了,它的作用就是在更新操作时提供“比较并替换”的作用。实际上就是AtomicInteger中的一个工具。
    private static final Unsafe unsafe = Unsafe.getUnsafe();
   //valueOffset是用来记录value本身在内存的便宜地址的,这个记录,也主要是为了在更新操作在内存中找到value的位置,方便比较。
    private static final long valueOffset;
   //value是用来存储整数的时间变量,这里被声明为volatile,就是为了保证在更新操作时,当前线程可以拿到value最新的值(并发环境下,value可能已经被其他线程更新了)。
    private volatile int value;

AtomicInteger 构造方法

 /**
     * Creates a new AtomicInteger with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    /**
     * Creates a new AtomicInteger with initial value {@code 0}.
     */
    public AtomicInteger() {
    }

AtomicInteger 并发安全实现

那么AtomicInteger是如何实现多线程的自增操作的线程安全的呢?核心思想就是CAS自旋;CAS:Compare And Swap 比较并交换。自旋:通过循环知道预期值和内存之相同,进行CAS操作,AtomicInteger的自增如下所示

       public final int incrementAndGet() {  
           for (;;) {  
               //这里可以拿到value的最新内存值
               int current = get();  
               int next = current + 1;  
               if (compareAndSet(current, next))  
                   return next;  
           }  
       }  
      
       public final boolean compareAndSet(int expect, int update) {  
           //使用unsafe的native方法,实现高效的硬件级别CAS  
       return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
       }  

 

 AtomicInteger 其他常用方法

  /**
     * 返回旧值,然后自增1
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

    /**
     * 返回旧值,然后自减1
     *
     * @return the previous value
     */
    public final int getAndDecrement() {
        return unsafe.getAndAddInt(this, valueOffset, -1);
    }

    /**
     * 返回旧值,然后  旧值+delta
     *
     * @param delta the value to add
     * @return the previous value
     */
    public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

    /**
     * 先进行自增,返回自增后的值
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

    /**
     * 先进行自减,然后返回自减后的值
     *
     * @return the updated value
     */
    public final int decrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
    }

    /**
     *先在原值上加delta,再返回加之后的值
     *
     * @param delta the value to add
     * @return the updated value
     */
    public final int addAndGet(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
    }

注:其他Atomic类类似,不一一介绍;

四 Atomic 存在的问题

    》 长时间自旋,导致CPU和资源的占用

  》 只能保证单个原子变量的多线程安全操作,当然可以将多个变量封装成一个类,通过原子引用类型实现

  》 ABA问题;使用AtomicStampedReference 原子更新带有版本号的引用类型解决

 

posted on 2016-09-25 20:19  Jerry迎风  阅读(318)  评论(0编辑  收藏  举报

导航