`

atomic包

 
阅读更多

转载自:http://blog.csdn.net/zhangerqing/article/details/43057799

 

Atomic简介

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

 

传统锁的问题

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

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. class Counter {  
  2.           
  3.     private int value;  
  4.   
  5.     public synchronized int getValue() {  
  6.         return value;  
  7.     }  
  8.   
  9.     public synchronized int increment() {  
  10.         return ++value;  
  11.     }  
  12.   
  13.     public synchronized int decrement() {  
  14.         return --value;  
  15.     }  
  16. }   


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

 

 

硬件同步策略

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

 

Compare and swap(CAS)

当前的处理器基本都支持CAS,只不过每个厂家所实现的算法并不一样罢了,每一个CAS操作过程都包含三个运算符:一个内存地址V,一个期望的值A和一个新值B,操作的时候如果这个地址上存放的值等于这个期望的值A,则将地址上的值赋为新值B,否则不做任何操作。CAS的基本思路就是,如果这个地址上的值和期望的值相等,则给其赋予新值,否则不做任何事儿,但是要返回原值是多少。我们来看一个例子,解释CAS的实现过程(并非真实的CAS实现):

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. class SimulatedCAS {  
  2.     private int value;  
  3.   
  4.     public synchronized int getValue() {  
  5.         return value;  
  6.     }  
  7.     public synchronized int compareAndSwap(int expectedValue, int newValue) {  
  8.         int oldValue = value;  
  9.         if (value == expectedValue)  
  10.             value = newValue;  
  11.         return oldValue;  
  12.     }  
  13. }  


下面是一个用CAS实现的Counter

 

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public class CasCounter {  
  2.     private SimulatedCAS value;  
  3.   
  4.     public int getValue() {  
  5.         return value.getValue();  
  6.     }  
  7.   
  8.     public int increment() {  
  9.         int oldValue = value.getValue();  
  10.         while (value.compareAndSwap(oldValue, oldValue + 1) != oldValue)  
  11.             oldValue = value.getValue();  
  12.         return oldValue + 1;  
  13.     }  
  14. }  

 

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()

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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的实现:

 

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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. }  


这里直接调用一个叫Unsafe的类去处理,看来我们还需要继续看一下unsafe类的源码了。JDK8中sun.misc下UnSafe类,点击查看源码

 

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

 

Atomic中的CAS

从前面的解释得知,CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值,此处这个“原本的一个值”怎么来,我们看看AtomicInteger里的实现:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. // setup to use Unsafe.compareAndSwapInt for updates  
  2.     private static final Unsafe unsafe = Unsafe.getUnsafe();  
  3.     private static final long valueOffset;  
  4.   
  5.     static {  
  6.         try {  
  7.             valueOffset = unsafe.objectFieldOffset  
  8.                 (AtomicInteger.class.getDeclaredField("value"));  
  9.         } catch (Exception ex) { throw new Error(ex); }  
  10.     }  


这里用到UnSafe的一个方法objectFieldOffset(),查看源码:

 

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.      * Report the location of a given static field, in conjunction with {@link 
  3.      * #staticFieldBase}. 
  4.      * <p>Do not expect to perform any sort of arithmetic on this offset; 
  5.      * it is just a cookie which is passed to the unsafe heap memory accessors. 
  6.      * 
  7.      * <p>Any given field will always have the same offset, and no two distinct 
  8.      * fields of the same class will ever have the same offset. 
  9.      * 
  10.      * <p>As of 1.4.1, offsets for fields are represented as long values, 
  11.      * although the Sun JVM does not use the most significant 32 bits. 
  12.      * It is hard to imagine a JVM technology which needs more than 
  13.      * a few bits to encode an offset within a non-array object, 
  14.      * However, for consistency with other methods in this class, 
  15.      * this method reports its result as a long value. 
  16.      * @see #getInt(Object, long) 
  17.      */  
  18.     public native long objectFieldOffset(Field f);  


这个方法是用来拿到我们上文提到的这个“原来的值”的内存地址。是一个本地方法,返回值是valueOffset。它的参数field就是AtomicInteger里定义的value属性:

 

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. private volatile int value;  
  2.   
  3. /** 
  4.  * Creates a new AtomicInteger with the given initial value. 
  5.  * 
  6.  * @param initialValue the initial value 
  7.  */  
  8. public AtomicInteger(int initialValue) {  
  9.     value = initialValue;  
  10. }  
  11.   
  12. /** 
  13.  * Creates a new AtomicInteger with initial value {@code 0}. 
  14.  */  
  15. public AtomicInteger() {  
  16. }  


value是一个volatile变量,在内存中可见,任何线程都不允许对其进行拷贝,因此JVM可以保证任何时刻任何线程总能拿到该变量的最新值。此处value的值,可以在AtomicInteger类初始化的时候传入,也可以留空,留空则自动赋值为0。

 

 

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

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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.    }  

 

继续:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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.     }  

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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的线程安全机制很好很高效,但要说的是,并非所有线程安全都可以用这样的方法来实现,这只适合一些粒度比较小,型如计数器这样的需求用起来才有效,否则也不会有锁的存在了。

 

分享到:
评论

相关推荐

    Java多线程atomic包介绍及使用方法

    Java 多线程 atomic 包介绍及使用方法 Java 多线程 atomic 包是 Java 并发编程中的一种重要工具,自从 JDK 1.5 开始,Java 就提供了 java.util.concurrent.atomic 包,方便程序员在多线程环境下,无锁的进行原子...

    浅谈Java中的atomic包实现原理及应用

    Java中的atomic包实现原理及应用 本文主要介绍了Java中的atomic包实现原理及应用,涉及Atomic在硬件上的支持、Atomic包简介及源码分析等相关内容。 Atomic在硬件上的支持 在单处理器系统中,能够在单条指令中...

    并发编程atomic&collections-课上笔记1

    本文主要讲述了 Java 中的并发编程,包括 atomic 包的介绍、CAS 算法的原理、ABA 问题的解决方案,以及 collections 中的 HashMap、HashTable 和 ConcurrentHashMap 的源码分析。 Atomic 包的介绍 ----------------...

    Java多线程Atomic包操作原子变量与原子类详解

    Java的`Atomic`包是Java多线程编程中一个重要的工具,它提供了对原子变量的操作,确保了在并发环境下的数据一致性。在多线程环境中,原子性是至关重要的,这意味着一个操作要么完整执行,要么不执行,不会被其他线程...

    go并发编程基础

    **atomic包**:在某些情况下,我们可能需要在不使用锁的情况下实现原子操作,Go的`atomic`包提供了这样的功能。它包含了一系列原子操作函数,如`AddInt32`、`CompareAndSwapInt32`等,可以在多goroutine环境下保证...

    boost_atomic.7z

    《深入理解Boost库:探索原子操作atomic》 在软件开发中,尤其是在多线程和并发编程领域,确保数据的一致性和完整性是至关重要的。Boost库,作为C++的一个强大工具集,提供了各种实用的组件来解决这些问题。其中,`...

    The-Golang-Standard-Library-by-Example-master.zip

    sync包提供了互斥锁、读写锁、等待组等工具,而atomic包提供了原子操作,保证了多线程环境下的数据一致性。 6. **context** 包:在处理长时间运行的请求时,如网络请求,用于传递取消信号、超时信息等上下文信息,...

    开源项目-golang-go.zip

    综上所述,这个开源项目涉及Go语言的编译器优化问题,特别是与并发安全相关的sync/atomic包的使用。理解这些问题对于编写高效、安全的Go代码至关重要,同时也展示了开源社区如何协同解决问题,推动项目发展。

    Atomic Alarm Clock6.3 64位安装包 + 破解包

    Atomic Alarm Clock是 Clock Tray Skins 的同胞软件,具有 Clock Tray Skins 的全部功能,可以替换美化并增强系统自带的任务栏时钟,显示的时间更加仔细,包含月、日、星期、时、分与秒及日历显示、时区等,界面漂亮...

    go语言开发技巧入门教程总结.docx

    使用原子操作(sync/atomic包);遵循最小权限原则,尽量减少共享数据。 错误处理 问题描述:错误处理代码冗余,影响可读性。 解决方案:采用简洁的错误处理模式,如Defer+Error组合;使用errgroup包聚合 goroutine

    atomic_data.tgz

    《ATOMIC:机器常识推理的综合图谱》 在当今的自然语言处理(NLP)领域,理解人类的常识推理是关键挑战之一。"atomic_data.tgz" 是一个专门针对这一问题的数据集,名为 ATOMIC(An Atlas of Machine Commonsense ...

    Lock、Synchoronized和ReentrantLock的使用

    使用 Atomic 包的 AtomicInteger 速度是比 Lock 要快 1 个数量级。 五、使用场景 * Synchronized:在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,Synchronized 是很合适的。 * ReentrantLock:在资源竞争...

    Lock free 论文集合,若干无锁数据结构实现的经典论文,500多页.zip

    Java提供了java.util.concurrent.atomic包,包含各种原子类,如AtomicInteger、AtomicReference等,方便开发者构建无锁数据结构。Python虽然没有内置的无锁原语,但可以通过使用Cython或者第三方库如threading....

    atomic

    在IT行业中,"atomic"这个概念可能涉及到多个领域,但在这里与"字体"标签结合,我们可以推断讨论的是Atomic Design理论在网页设计或用户界面(UI)设计中的应用,特别是与字体相关的原子组件。Atomic Design是一种...

    Golang标准库-180910.7z

    13. **并发安全的数据结构**:sync和sync/atomic包提供了互斥锁、信号量、原子操作等同步原语,确保并发环境下的数据一致性。 14. **Go汇编语言**:asm和cmd/asm包提供了与Go语言相关的汇编语言支持。 通过这个CHM...

    java.util.concurrent 测试源文件

    5. **原子类**:Atomic包下的类,如AtomicInteger、AtomicLong等,提供了基于CAS(Compare and Swap,比较并交换)操作的无锁编程机制。这些原子变量类在多线程环境中可以进行高效且线程安全的更新。 6. **并发工具...

    Atom-atom,安全的原子指针。为幻灯片做贡献.zip

    原子操作(Atomic Operations)通常由操作系统或编程语言的库提供,如C++的std::atomic或Java的java.util.concurrent.atomic包。在Atom中,这样的原子操作可能用于确保编辑器的内部状态在多个进程或线程之间正确同步...

    Atomic

    在IT领域,"Atomic"可能指的是多个概念,但在这个场景中,由于标签是"字体",我们可以推测这里的"Atomic"是指一种特定的字体或者排版风格。字体在计算机科学和设计行业中扮演着至关重要的角色,它影响着用户对数字...

    JUC包含线程,线程池,CAS,volatile等底层原理,以及相关问题的解决方式以及相关工具类的使用

    为了解决原子性问题,可以使用Java的Atomic包,如AtomicInteger、AtomicLong等,它们基于CAS操作,可以避免指令重排,保证多线程环境中的数据一致性。 **锁机制** 除了volatile,Java还提供了Lock接口及其实现,如...

    Atom-atomic-blonde,源程序包.zip

    Atom-atomic-blonde.zip,SourceKit-based syntax highlighting for the Swift languageAtomic Blonde是一种基于SourceKit的用于Swift语言的语法增强器。与由正则表达式语法支持的highlighter不同,atomic blonde调用...

Global site tag (gtag.js) - Google Analytics