并发之java.util.concurrent.atomic原子操作类包

一.java.util.concurrent.atomic原子操作类包

二.Java多线程-java.util.concurrent.atomic包原理解读

三.JDK1.8的Java.util.concurrent.atomic包小结

四.Java中Atomic包的原理和分析

-------

一.java.util.concurrent.atomic原子操作类包

这个包里面提供一组原子变量类。
其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。
实际上是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。可以对基本数据、数组中的基本数据、对类中的基本数据进行操作。原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的读-改-写操作。

 

    java.util.concurrent.atomic中的类可以分成4组:

  • 标量类(Scalar):AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  • 数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
  • 更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
  • 复合变量类:AtomicMarkableReference,AtomicStampedReference

第一组AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference这四种基本类型用来处理布尔,整数,长整数,对象四种数据,其内部实现不是简单的使用synchronized,而是一个更为高效的方式CAS (compare and swap) + volatile和native方法,从而避免了synchronized的高开销,执行效率大为提升。
如AtomicInteger的实现片断为:

  1. private static final Unsafe unsafe = Unsafe.getUnsafe();  
  2. private volatile int value;  
  3. public final int get() {  
  4.         return value;  
  5. }  
  6. public final void set(int newValue) {  
  7.         value = newValue;  
  8. }  
  9. public final boolean compareAndSet(int expect, int update) {  
  10.     return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
  11. }  
  • 构造函数(两个构造函数)set( )和get( )方法:可以原子地设定和获取atomic的数据。类似于volatile,保证数据会在主存中设置或读取
    • 默认的构造函数:初始化的数据分别是false,0,0,null
    • 带参构造函数:参数为初始化的数据
  • void set()和void lazySet():set设置为给定值,直接修改原始值; lazySet延时设置变量值,这个等价于set(),但是由于字段是volatile类型的,因此次字段的修改会比普通字段(非volatile字段)有稍微的性能延时(尽管可以忽略),所以如果不是想立即读取设置的新值,允许在“后台”修改值,那么此方法就很有用。
  • getAndSet( )方法
    • 原子的将变量设定为新数据,同时返回先前的旧数据
    • 其本质是get( )操作,然后做set( )操作。尽管这2个操作都是atomic,但是他们合并在一起的时候,就不是atomic。在Java的源程序的级别上,如果不依赖synchronized的机制来完成这个工作,是不可能的。只有依靠native方法才可以。
    1. public final int getAndSet(int newValue) {  
    2.     for (;;) {  
    3.         int current = get();  
    4.         if (compareAndSet(current, newValue))  
    5.             return current;  
    6.     }  
    7. }  
  • compareAndSet( ) 和weakCompareAndSet( )方法对于 AtomicInteger、AtomicLong还提供了一些特别的方法。
    • 这两个方法都是conditional modifier方法。这2个方法接受2个参数,一个是期望数据(expected),一个是新数据(new);如果atomic里面的数据和期望数据一 致,则将新数据设定给atomic的数据,返回true,表明成功;否则就不设定,并返回false。

    JSR规范中说:以原子方式读取和有条件地写入变量但不 创建任何 happen-before 排序,因此不提供与除 weakCompareAndSet 目标外任何变量以前或后续读取或写入操作有关的任何保证。
    大意就是说调用weakCompareAndSet时并不能保证不存在happen- before的发生(也就是可能存在指令重排序导致此操作失败)。但是从Java源码来看,其实此方法并没有实现JSR规范的要求,最后效果和 compareAndSet是等效的,都调用了unsafe.compareAndSwapInt()完成操作。

    1. public final boolean compareAndSet(int expect, int update) {  
    2.     return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
    3. }  
    4. public final boolean weakCompareAndSet(int expect, int update) {  
    5.     return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
    6. }  

  • getAndIncrement( ):以原子方式将当前值加 1,相当于线程安全的i++操作。 
    incrementAndGet( ):以原子方式将当前值加 1, 相当于线程安全的++i操作。
    getAndDecrement( ):以原子方式将当前值减 1, 相当于线程安全的i--操作。
    decrementAndGet ( ):以原子方式将当前值减 1,相当于线程安全的--i操作。 
    addAndGet( ): 以原子方式将给定值与当前值相加, 实际上就是等于线程安全的i =i+delta操作。
    getAndAdd( ):以原子方式将给定值与当前值相加, 相当于线程安全的t=i; i+=delta; return t;操作。
    以实现一些加法,减法原子操作。(注意 --i、++i不是原子操作,其中包含有3个操作步骤:第一步,读取i; 第二步,加1或减1;第三步:写回内存)

使用AtomicReference创建线程安全的堆栈

  1. import java.util.concurrent.atomic.AtomicReference;  
  2. public class ConcurrentStack<T> {  
  3. private AtomicReference<Node<T>>    stacks  = new AtomicReference<Node<T>>();  
  4. public T push(T e) {  
  5.         Node<T> oldNode, newNode;  
  6. for (;;) { // 这里的处理非常的特别,也是必须如此的。  
  7.             oldNode = stacks.get();  
  8.             newNode = new Node<T>(e, oldNode);  
  9. if (stacks.compareAndSet(oldNode, newNode)) {  
  10. return e;  
  11.             }  
  12.         }  
  13.     }     
  14. public T pop() {  
  15.         Node<T> oldNode, newNode;  
  16. for (;;) {  
  17.             oldNode = stacks.get();  
  18.             newNode = oldNode.next;  
  19. if (stacks.compareAndSet(oldNode, newNode)) {  
  20. return oldNode.object;  
  21.             }  
  22.         }  
  23.     }     
  24. private static final class Node<T> {  
  25. private T       object;       
  26. private Node<T>   next;         
  27. private Node(T object, Node<T> next) {  
  28. this.object = object;  
  29. this.next = next;  
  30.         }  
  31.     }     
  32. }  

      虽然原子的标量类扩展了Number类,但并没有扩展一些基本类型的包装类,如Integer或Long,事实上他们也不能扩展:基本类型的包装类是不可以修改的,而原子变量类是可以修改的。在原子变量类中没有重新定义hashCode或equals方法,每个实例都是不同的,他们也不宜用做基于散列容器中的键值。 

      第二组AtomicIntegerArray,AtomicLongArray还有AtomicReferenceArray类进一步扩展了原子操作,对这些类型的数组提供了支持。这些类在为其数组元素提供 volatile 访问语义方面也引人注目,这对于普通数组来说是不受支持的。

他们内部并不是像AtomicInteger一样维持一个valatile变量,而是全部由native方法实现,如下
AtomicIntegerArray的实现片断:

  1. private static final Unsafe unsafe = Unsafe.getUnsafe();  
  2. private static final int base = unsafe.arrayBaseOffset(int[].class);  
  3. private static final int scale = unsafe.arrayIndexScale(int[].class);  
  4. private final int[] array;  
  5. public final int get(int i) {  
  6.         return unsafe.getIntVolatile(array, rawIndex(i));  
  7. }  
  8. public final void set(int i, int newValue) {  
  9.         unsafe.putIntVolatile(array, rawIndex(i), newValue);  
  10. }  

第三组 AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater基于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新。API非常简单,但是也是有一些约束:

(1)字段必须是volatile类型的

(2)字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致。也就是说 调用者能够直接操作对象字段,那么就可以反射进行原子操作。但是对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。

(3)只能是实例变量,不能是类变量,也就是说不能加static关键字。

(4)只能是可修改变量,不能使final变量,因为final的语义就是不可修改。实际上final的语义和volatile是有冲突的,这两个关键字不能同时存在。

(5)对于AtomicIntegerFieldUpdater 和AtomicLongFieldUpdater 只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater 。

 netty5.0中类ChannelOutboundBuffer统计发送的字节总数,由于使用volatile变量已经不能满足,所以使用AtomicIntegerFieldUpdater 来实现的,看下面代码:

  1.     private static final AtomicLongFieldUpdater<ChannelOutboundBuffer> TOTAL_PENDING_SIZE_UPDATER =  
  2.             AtomicLongFieldUpdater.newUpdater(ChannelOutboundBuffer.class, "totalPendingSize");  
  3.     private volatile long totalPendingSize;  
  4.     //使用  
  5.         long oldValue = totalPendingSize;  
  6.         long newWriteBufferSize = oldValue + size;  
  7.         while (!TOTAL_PENDING_SIZE_UPDATER.compareAndSet(this, oldValue, newWriteBufferSize)) {  
  8.             oldValue = totalPendingSize;  
  9.             newWriteBufferSize = oldValue + size;  
  10.         }  

2.Java多线程-java.util.concurrent.atomic包原理解读

参考学习:http://blog.csdn.net/zhangerqing/article/details/43057799
多线程基本类型
AtomicReference
Atomic*: 
Atomic包是java.util.concurrent下的另一个专门为线程安全设计的Java包,包含多个原子操作类,但Atomic的线程安全是如何来实现的呢?
1、硬件同步策略
现在的处理器都支持多重处理,当然也包含多个处理器共享外围设备和内存,同时,加强了指令集以支持一些多处理的特殊需求。
特别是几乎所有的处理器都可以将其他处理器阻塞以便更新共享变量
2、Compare and swap(CAS)
当前的处理器基本都支持CAS,只不过每个厂家所实现的算法并不一样罢了,每一个CAS操作过程都包含三个运算符:一个内存地址V,
一个期望的值A和一个新值B,操作的时候如果这个地址上存放的值等于这个期望的值A,则将地址上的值赋为新值B,否则不做任何操作。
CAS的基本思路就是,如果这个地址上的值和期望的值相等,则给其赋予新值,否则不做任何事儿,但是要返回原值是多少。我们来看一个例子,
解释CAS的实现过程(并非真实的CAS实现)

3、实现的核心源码:
   /**
     * Atomically increments by one the current value.
     * @return the updated value
     */
    public final int incrementAndGet() {
        for (;;) {
            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);
    }
他比直接使用传统的java锁机制(阻塞的)有什么好处?
最大的好处就是可以避免多线程的优先级倒置和死锁情况的发生,当然高并发下的性能提升也是很重要的

4、CAS线程安全
说了半天,我们要回归到最原始的问题了:这样怎么实现线程安全呢?请大家自己先考虑一下这个问题,其实我们在语言层面是没有做任何同步的操作的,
大家也可以看到源码没有任何锁加在上面,可它为什么是线程安全的呢?这就是Atomic包下这些类的奥秘:语言层面不做处理,我们将其交给硬件—CPU和内存,
利用CPU的多处理能力,实现硬件层面的阻塞,再加上volatile变量的特性即可实现基于原子操作的线程安全。所以说,CAS并不是无阻塞,
只是阻塞并非在语言、线程方面,而是在硬件层面,所以无疑这样的操作会更快更高效!

5总结
虽然基于CAS的线程安全机制很好很高效,但要说的是,并非所有线程安全都可以用这样的方法来实现,这只适合一些粒度比较小,
如计数器这样的需求用起来才有效,否则也不会有锁的存在了。

 

二.JDK1.8的Java.util.concurrent.atomic包小结

Atomic意为原子的, JUC包又是并发包。
Atomic的特点:
①多线程环境下,无所的进行原子操作。
②不能绝对保证线程不被阻塞。(因不同CPU的原子指令不同,可能需要某种形式的内部锁)

总结: J.U.C(java.util.concurrent)原子类分类
根据修改的数据类型,可以将JUC包中的原子操作类可以分为4类。
1. 基本类型: AtomicInteger, AtomicLong, AtomicBoolean ;
2. 数组类型: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray ;
3. 引用类型: AtomicReference, AtomicStampedRerence, AtomicMarkableReference ;
4. 对象的属性修改类型: AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater 。

JDK1.5中:
原子性更新基本类型:
AtomicBoolean:原子更新布尔类型。
AtomicInteger:原子更新整型。
AtomicLong:原子更新长整型。
原子更新基本类型数组:
AtomicIntegerArray:原子更新整型数组里的元素。
AtomicLongArray:原子更新长整型数组里的元素。
更新字段:
抽象类:AtomicIntegerFieldUpdater: 原子更新整型字段的更新器。
抽象类:AtomicLongFieldUpdater: 原子更新长整型字段的更新器。
抽象类:AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
更新引用类型:
AtomicReference:原子更新引用类型。
AtomicMarkableReference 原子更新带有标记位的引用类型
AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号,
可以解决使用CAS进行原子更新时,可能出现的ABA问题。
更新引用类型数组:
AtomicReferenceArray: 原子更新引用类型数组里的元素。
JDK1.8中:Striped64
JDK 8 的 java.util.concurrent.atomic 下有一个包本地的类 Striped64 ,它持有常见表示和机制,用于类支持动态 striping 到 64bit 值上。

strping:
数据 striping 就是把逻辑上连续的数据分为多个段,使这一序列的段存储在不同的物理设备上。通过把段分散到多个设备上可以增加访问并发性,从而提升总体的吞吐量。

累加器:
DoubleAccumulator
DoubleAdder
LongAccumulator
LongAdder
1.8中的Double和Long累加器 将原本的
如Integer中
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
do while体系 变为
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
longAccumulate(x, null, uncontended);
}
}
参考:http://www.cnblogs.com/davidwang456/p/4670777.html

1. 在标量 如 boolean,integer,long,reference.
其底层是CAS (compare and swap) + volatile 和native方法,从而避免了synchronized的高开销,执行效率大为提升
2. AtomicIntegerArray,AtomicLongArray还有AtomicReferenceArray类进一步扩展了原子操作,对这些类型的数组提供了支持。
这些类在为其数组元素提供 volatile 访问语义方面也引人注目,这对于普通数组来说是不受支持的。他们内部并不是像AtomicInteger一样维持一个valatile变量,而是全部由native方法实现,如下 AtomicIntegerArray的实现片断
3. AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater基于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新。API非常简单,但是也是有一些约束:
(1)字段必须是volatile类型的
(2)字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致。也就是说调用者能够直接操作对象字段,那么就可以反射进行原子操作。但是对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。
(3)只能是实例变量,不能是类变量,也就是说不能加static关键字。
(4)只能是可修改变量,不能使final变量,因为final的语义就是不可修改。实际上final的语义和volatile是有冲突的,这两个关键字不能同时存在。
(5)对于AtomicIntegerFieldUpdater 和AtomicLongFieldUpdater 只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater 。
netty5.0中类ChannelOutboundBuffer统计发送的字节总数,由于使用volatile变量已经不能满足,所以使用AtomicIntegerFieldUpdater 来实现的,看下面代码:
Atomic下有哪些类
13个实现类:
AtomicBoolean @since 1.5
自己翻译: 一、可能以原子方式更新。
二、不能当作Boolean的替代品
AtomicInteger特点:
自己翻译: 一、可能以原子方式更新。
二、不能当作Integer的替代品
三、类继承自Number,允许被以数值为基础的 classes文件 当做工具类去使用
AtomicIntegerArray @since 1.5 特点:
自己翻译: 可能以原子方式更新。
这个类通常被用作
AtomicLong 特点:
自己翻译: 一、可能以原子方式更新。
二、不能当作Long的替代品
三、类继承自Number,允许被以数值为基础的 classes文件 当做工具类去使用
AtomicLongArray
特点:
自己翻译: 可能以原子方式更新。
这个类通常被用作
AtomicMarkableReference
一、维护一个对象引用,加上一个标记位,除了标记位外,还可以进行原子更新。
二、该实现通过创建表示“装箱”(引用、布尔)对的内部对象来保持标记引用。
AtomicReference @since 1.5
一、可以以原子方式更新的对象引用
AtomicReferenceArray @since 1.5
一、可以以原子方式更新的对象引用
AtomicStampedReference @since 1.5
一、保持一个对象引用和一个整数“戳记”,可以以原子的方式更新。
二、实现说明:这个实现通过创建表示“装箱”(引用、整数)对的内部对象来维护标记引用。
DoubleAccumulator @since 1.8
一个或多个变量,共同维护一个运行的{@ code double}值,使用提供的函数更新。当更新(方法{@ link #积累})在线程之间进行争用时,变量集可以动态增长以减少争用。方法{@ link # get}(或者,相当地,{@ link # doubleValue})返回当前的值,以保持更新。
当多个线程更新一个共同的值时,这个类通常比其他方法更可取,比如经常更新但不经常读取的汇总统计信息。
提供的累加器函数应该是无副作用的,因为在尝试更新失败时,由于线程之间的争用,它可能会被重新应用。函数以当前值作为其第一个参数,而给定的update作为第二个参数。例如,为了维持一个运行最大值,您可以提供{@ code Double::max}和{@ code Double。NEGATIVE_INFINITY }的身份。在线程内部或跨线程中积累的顺序没有保证。因此,这
如果需要数值稳定性,则类可能不适用,特别是当组合了大量不同的值时
的大小。类{@ link DoubleAdder}为维护和的共同特殊情况提供了该类的类似功能。调用{@ code新的DoubleAdder()}相当于{@ code new double累加器((x,y)- > x + y,0.0)}。
这个类扩展了{@ link Number},但是< em >不是< /em >定义了诸如{@ code =}、{@ code hashCode}和{@ code compareTo}之类的方法,因为实例被期望发生突变,因此不作为集合键有用。

DoubleAdder @since 1.8
一个或多个变量一起维护一个初始的零{@ code double} sum。当更新(方法{@ link # add})在线程之间进行争用时,变量集可以动态增长以减少争用。
方法{@ link # sum}(或者,等价{@ link # doubleValue})将当前的总数组合在维持总和的变量上。在线程内部或跨线程中积累的顺序没有保证。
因此,如果需要数值稳定性,这个类可能不适用,特别是当组合了大量不同数量级的值时。
当多个线程更新一个共同的值时,这个类通常比其他方法更可取,比如经常更新但不太频繁的汇总统计数据
这个类扩展了{@ link Number},但是< em >不是< /em >定义了诸如{@ code =}、{@ code hashCode}和{@ code compareTo}之类的方法,因为实例被期望发生突变,因此不作为集合键有用。
LongAccumulator
LongAdder

4个抽象类
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
AtomicReferenceFieldUpdater
Striped64 1.8加入的

 

三.Java中Atomic包的原理和分析

Atomic简介

Atomic包是Java.util.concurrent下的另一个专门为线程安全设计的Java包,包含多个原子操作类。这个包里面提供了一组原子变量类。

其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。

实际上是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。可以对基本数据、数组中的基本数据、对类中的基本数据进行操作。

原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的读-改-写操作。——  引自@chenzehe 的博客。

传统锁的问题

我们先来看一个例子:计数器(Counter),采用Java里比较方便的锁机制synchronized关键字,初步的代码如下:

  1. class Counter {   
  2.     private int value;  
  3.   
  4.     public synchronized int getValue() {  
  5.         return value;  
  6.     }  
  7.   
  8.     public synchronized int increment() {  
  9.         return ++value;  
  10.     }  
  11.   
  12.     public synchronized int decrement() {  
  13.         return --value;  
  14.     }  
  15. }   
其实像这样的锁机制,满足基本的需求是没有问题的了,但是有的时候我们的需求并非这么简单,我们需要更有效,更加灵活的机制,synchronized关键字是基于阻塞的锁机制,也就是说当一个线程拥有锁的时候,访问同一资源的其它线程需要等待,直到该线程释放锁,这里会有些问题:
首先,如果被阻塞的线程优先级很高很重要怎么办?其次,如果获得锁的线程一直不释放锁怎么办?(这种情况是非常糟糕的)。
还有一种情况,如果有大量的线程来竞争资源,那CPU将会花费大量的时间和资源来处理这些竞争(事实上CPU的主要工作并非这些),
同时,还有可能出现一些例如死锁之类的情况,最后,其实锁机制是一种比较粗糙,粒度比较大的机制,相对于像计数器这样的需求有点儿过于笨重,
因此,对于这种需求我们期待一种更合适、更高效的线程安全机制。

硬件同步策略

现在的处理器都支持多重处理,当然也包含多个处理器共享外围设备和内存,同时,加强了指令集以支持一些多处理的特殊需求。特别是几乎所有的处理器都可以将其他处理器阻塞以便更新共享变量。

Compare and swap(CAS)

当前的处理器基本都支持CAS,只不过每个厂家所实现的算法并不一样罢了,每一个CAS操作过程都包含三个运算符:一个内存地址V,一个期望的值A和一个新值B,操作的时候如果这个地址上存放的值等于这个期望的值A,则将地址上的值赋为新值B,否则不做任何操作。

CAS的基本思路就是,如果这个地址上的值和期望的值相等,则给其赋予新值,否则不做任何事儿,但是要返回原值是多少。

一个例子,解释CAS的实现过程(并非真实的CAS实现):

  1. class SimulatedCAS {  
  2.     private int value;  
  3.     public synchronized int getValue() {  
  4.         return value;  
  5.     }  
  6.     public synchronized int compareAndSwap(int expectedValue, int newValue) {  
  7.         int oldValue = value;  
  8.         if (value == expectedValue)  
  9.             value = newValue;  
  10.         return oldValue;  
  11.     }  
  12. }  
下面是一个用CAS实现的Counter
  1. public class CasCounter {  
  2.     private SimulatedCAS value;  
  3.     public int getValue() {  
  4.         return value.getValue();  
  5.     }  
  6.     public int increment() {  
  7.         int oldValue = value.getValue();  
  8.         while (value.compareAndSwap(oldValue, oldValue + 1) != oldValue)  
  9.             oldValue = value.getValue();  
  10.         return oldValue + 1;  
  11.     }  
  12. }  

示例如下:

  1. public class Counter implements Runnable {  
  2.     private final AtomicInteger count = new AtomicInteger(0);  
  3.     public void run() {  
  4.         System.out.println(Thread.currentThread().getName()   
  5.                 + ":" + count.incrementAndGet());  
  6.     }  
  7.     public static void main(String[] args){  
  8.         Counter counter = new Counter();  
  9.         Thread t1 = new Thread(counter);  
  10.         Thread t2 = new Thread(counter);  
  11.         Thread t3 = new Thread(counter);  
  12.         Thread t4 = new Thread(counter);  
  13.         t1.start();  
  14.         t2.start();  
  15.         t3.start();  
  16.         t4.start();  
  17.     }  
  18. }  

看看源代码中究竟是如何实现的

  1. private volatile int value;  
  2. public AtomicInteger(int initialValue) {  
  3.     value = initialValue;  
  4. }  
  5. public final int incrementAndGet() {  
  6. for (;;) {  
  7. int current = get();  
  8. int next = current + 1;  
  9. if (compareAndSet(current, next))  
  10. return next;  
  11.     }  
  12. }  
  13. public final boolean compareAndSet(int expect, int update) {  
  14. return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
  15. }  
  16. public final int get() {  
  17. return value;  
  18. }  

是不是和乐观锁很像,如果结果符合预期结果,就将结果返回,否则不断进行重试,并没有进行同步,兼顾了安全性和性能
java.util.concurrent.atomic包下还有很多类,使用这些类可以保证对这些类的诸如“获取-更新”操作是原子性的,从而避发生竞态条件

  1. AtomicBoolean 可以用原子方式更新的 boolean 值。   
  2. AtomicInteger 可以用原子方式更新的 int 值。   
  3. AtomicIntegerArray 可以用原子方式更新其元素的 int 数组。   
  4. AtomicIntegerFieldUpdater<T> 基于反射的实用工具,可以对指定类的指定 volatile int 字段进行原子更新。   
  5. AtomicLong 可以用原子方式更新的 long 值。   
  6. AtomicLongArray 可以用原子方式更新其元素的 long 数组。   
  7. AtomicLongFieldUpdater<T> 基于反射的实用工具,可以对指定类的指定 volatile long 字段进行原子更新。   
  8. AtomicMarkableReference<V> AtomicMarkableReference 维护带有标记位的对象引用,可以原子方式对其进行更新。   
  9. AtomicReference<V> 可以用原子方式更新的对象引用。   
  10. AtomicReferenceArray<E> 可以用原子方式更新其元素的对象引用数组。   
  11. AtomicReferenceFieldUpdater<T,V> 基于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新。   
  12. AtomicStampedReference<V> AtomicStampedReference 维护带有整数“标志”的对象引用,可以用原子方式对其进行更新。   

Atomic类

在JDK5.0之前,想要实现无锁无等待的算法是不可能的,除非用本地库,自从有了Atomic变量类后,这成为可能。下图是java.util.concurrent.atomic包下的类结构。
  • 标量类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  • 数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
  • 更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
  • 复合变量类:AtomicMarkableReference,AtomicStampedReference

第一组AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference这四种基本类型用来处理布尔,整数,长整数,对象四种数据,其内部实现不是简单的使用synchronized,而是一个更为高效的方式CAS (compare and swap) + volatile和native方法,从而避免了synchronized的高开销,执行效率大为提升。我们来看个例子,与我们平时i++所对应的原子操作为:getAndIncrement()

  1. public static void main(String[] args) {  
  2.     AtomicInteger ai = new AtomicInteger();  
  3.     System.out.println(ai);  
  4.     ai.getAndIncrement();  
  5.     System.out.println(ai);  
  6. }  
我们可以看一下AtomicInteger的实现:
  1. /** 
  2.  * Atomically increments by one the current value. 
  3.  * @return the previous value 
  4.  */  
  5. public final int getAndIncrement() {  
  6.     return unsafe.getAndAddInt(this, valueOffset, 1);  
  7. }  
这里直接调用一个叫Unsafe的类去处理,看来我们还需要继续看一下unsafe类的源码了。JDK8中sun.misc下UnSafe类,点击查看源码

从源码注释得知,这个类是用于执行低级别、不安全操作的方法集合。尽管这个类和所有的方法都是公开的(public),但是这个类的使用仍然受限,你无法在自己的java程序中直接使用该类,因为只有授信的代码才能获得该类的实例。所以我们平时的代码是无法使用这个类的,因为其设计的操作过于偏底层,如若操作不慎可能会带来很大的灾难,所以直接禁止普通代码的访问,当然JDK使用是没有问题的。 

Atomic中的CAS

从前面的解释得知,CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值,

此处这个“原本的一个值”怎么来,我们看看AtomicInteger里的实现:

  1.     // setup to use Unsafe.compareAndSwapInt for updates  
  2.     private static final Unsafe unsafe = Unsafe.getUnsafe();  
  3.     private static final long valueOffset;  
  4.     static {  
  5.         try {  
  6.             valueOffset = unsafe.objectFieldOffset  
  7.                 (AtomicInteger.class.getDeclaredField("value"));  
  8.         } catch (Exception ex) { throw new Error(ex); }  
  9.     }  

这里用到UnSafe的一个方法objectFieldOffset(),查看源码:
  1. public native long objectFieldOffset(Field f);  
这个方法是用来拿到我们上文提到的这个“原来的值”的内存地址。是一个本地方法,返回值是valueOffset。它的参数field就是AtomicInteger里定义的value属性:
  1. private volatile int value;  
  2. /** 
  3.  * Creates a new AtomicInteger with the given initial value. 
  4.  * @param initialValue the initial value 
  5.  */  
  6. public AtomicInteger(int initialValue) {  
  7.     value = initialValue;  
  8. }  
  9. /** 
  10.  * Creates a new AtomicInteger with initial value {@code 0}. 
  11.  */  
  12. public AtomicInteger() {  
  13. }  
value是一个volatile变量,在内存中可见,任何线程都不允许对其进行拷贝,因此JVM可以保证任何时刻任何线程总能拿到该变量的最新值。此处value的值,可以在AtomicInteger类初始化的时候传入,也可以留空,留空则自动赋值为0。 

我们再回到CAS,看看getAndIncrement()方法是怎么利用CAS实现的。

  1.    /** 
  2.     * Atomically increments by one the current value. 
  3.     * 
  4.     * @return the previous value 
  5.     */  
  6.    public final int getAndIncrement() {  
  7.        return unsafe.getAndAddInt(this, valueOffset, 1);  
  8.    }  

继续:

  1. public final int getAndAddInt(Object o, long offset, int delta) {  
  2.         int v;  
  3.         do {  
  4.             v = getIntVolatile(o, offset);   //------------0---------------  
  5.         } while (!compareAndSwapInt(o, offset, v, v + delta));  //-------------1-------------  
  6.         return v;  
  7.     }  
  1. /** 
  2.    * Atomically update Java variable to <tt>x</tt> if it is currently 
  3.    * holding <tt>expected</tt>. 
  4.    * @return <tt>true</tt> if successful 
  5.    */  
  6.   public final native boolean compareAndSwapInt(Object o, long offset, //---------------2--------------  
  7.                                                 int expected,  
  8.                                                 int x);  
我稍微解释一下,其实compareAndSwapInt的注释解释很明确,原子的将变量的值更新为x,如果成功了返回true,我们知道,如果我们创建AtomicInteger实例时不传入参数,则原始变量的值即为0,所以上面//----------0-----------处得到的v的值即为0,1处的代码为:

while(!compareAndSwapInt(o, offset, 0, 1))我们知道offset指向的地址对应的值就是原始变量的初值0,所以与期望的值0相同,所以将初值赋值为1,返回true,取反后为false,循环结束,返回v即更新之前的值0. 这就是类似于i++操作的原子操作的实现,当然最终CAS的实现都是native的,用C语言实现的,我们这里看不到源码,有时间我会反编译一下这段代码看看。

CAS线程安全

说了半天,回归最原始问题:这样怎么实现线程安全呢?其实我们在语言层面是没有做任何同步的操作的,大家也可以看到源码没有任何锁加在上面,可它为什么是线程安全的呢?
这就是Atomic包下这些类的奥秘:语言层面不做处理,我们将其交给硬件—CPU和内存,利用CPU的多处理能力,实现硬件层面的阻塞,再加上volatile变量的特性即可实现基于原子操作的线程安全。所以说,CAS并不是无阻塞,只是阻塞并非在语言、线程方面,而是在硬件层面,所以无疑这样的操作会更快更高效!
总结:
虽然基于CAS的线程安全机制很好很高效,但要说的是,并非所有线程安全都可以用这样的方法来实现,这只适合一些粒度比较小,型如计数器这样的需求用起来才有效,否则也不会有锁的存在了。

posted on 2018-06-23 13:14  左手指月  阅读(696)  评论(0编辑  收藏  举报