美文网首页我爱编程
JDK 源码解析 —— AtomicInteger

JDK 源码解析 —— AtomicInteger

作者: 01_小小鱼_01 | 来源:发表于2018-05-26 23:26 被阅读23次

    java.util.concurrent.atomic包里面的类主要是用于在高并发环境下的高效程序处理,来帮助我们简化同步处理。

    AtomicInteger是一个提供原子操作的Integer类,在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口,使用AtomicInteger是非常的安全的.而且因为AtomicInteger由硬件提供原子操作指令实现的。在非激烈竞争的情况下,开销更小,速度更快。
    使用实例:

    class TestAtomicInteger {
            private AtomicInteger count = new AtomicInteger(1);
    
            public void increment() {
                      count.incrementAndGet();
            }
    
            //不需要加锁也实现线程安全。
           public int getCount() {
                    return count.get();
            }
    }
    

    首先,让我们来看看AtomicInteger的源代码

    // 继承了 Number, 这主要是提供方法将数值转化为 byte, double, long等基本数据类型。
    public class AtomicInteger extends Number implements java.io.Serializable  {
        private static final long serialVersionUID = 6214790243416807050L;
        // 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); }
        }
        // volatile 关键字修饰, 使 value 变量的改变具有可见性, 
        // 底层实现是内存栅栏,保证每次取到的是最新值。
        private volatile int value;
    
        public final int get() {  
            return value;  
        }  
    
        public final int getAndDecrement() {  
            // 可以看到 for 是一个死循环, 是采用忙等(也叫自旋)的方式
            // 不断地尝试(乐观锁)-1 操作, 直到成功才退出。 
            for (;;) {  
                int current = get();  
                int next = current - 1;  
                if (compareAndSet(current, next))  
                    return current;  
            }  
        }  
    
        // 利用 JNI 调用底层其他语言实现的方法, 
        // 利用操作系统提供的 CAS(只要当前值和原来不一致就重新取值直到成功) 来保证原子性。
        public final boolean compareAndSet(int expect, int update) {  
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
        }  
    }
    

    在这里有必要提一下CAS的操作:CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。

    CAS 的缺点
    • ABA问题。因为 CAS 需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
    • 循环时间长开销大。自旋 CAS 如果长时间不成功,会给 CPU 带来非常大的执行开销。如果JVM能支持处理器提供的 pause 指令那么效率会有一定的提升,pause 指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使 CPU 不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation:内存顺序冲突一般是由伪/假共享引起,假共享是指多个 CPU 同时修改同一个缓存行的不同部分而引起其中一个CPU的操作无效,当出现这个内存顺序冲突时,CPU必须清空流水线)而引起 CPU 流水线被清空(CPU pipeline flush),从而提高 CPU 的执行效率。
    • 只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

    参考博客
    1. CAS操作
    2. Java中CAS详解

    相关文章

      网友评论

        本文标题:JDK 源码解析 —— AtomicInteger

        本文链接:https://www.haomeiwen.com/subject/eunejftx.html