精华内容
下载资源
问答
  • java原子类

    2020-01-04 20:03:54
    什么是原子类 原子是最小粒度的,操作不可分割的。原子类作用 对多线程访问同一个变量,我们需要加锁,而锁是比较消耗性能的,JDk1.5之后, 新增的原子操作提供了 一种用法简单、性能高效、线程安全地更新一个变量...

    什么是原子类
    原子是最小粒度的,操作不可分割的。

    原子类作用
    对多线程访问同一个变量,我们需要加锁,而锁是比较消耗性能的,JDk1.5之后, 新增的原子操作类提供了
    一种用法简单、性能高效、线程安全地更新一个变量的方式, 这些类同样位于JUC包下的atomic包下,发展
    到JDk1.8,该包下共有17个类, 囊括了原子更新基本类型、原子更新数组、原子更新属性、原子更新引用

    jdk1.8新增原子类
    DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder、Striped64
    到目前为止在java.util.concurrent.atomic包下提供了17个原子类
    可分为4类:基本原子类,数组原子类、引用原子类、字段原子类

    ①基本原子类
    布尔型:AtomicBoolean
    整型:AtomicInteger
    长整形:AtomicLong
    ②数组原子类
    整型数组:AtomicIntegerArray
    长整型数组:AtomicLongArray
    引用型数组:AtomicReferenceArray
    ③引用原子类
    AtomicReference:原子更新引用类型
    AtomicReferenceFieldUpdater:原子更新引用类型里的字段
    AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子的更新一个布尔类型的标记位和引用类型。构造方         法是AtomicMarkableReference(V initialRef, boolean initialMark)
    ④字段原子类
    AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
    AtomicLongFieldUpdater:原子更新长整型字段的更新器
    AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号,可以解决使用CAS进行原子更新时,可能出现的ABA问题

    基本原子类都是volatile value+cas比较简单略过。
    数组原子类以AtomicIntegerArray为例,主要讲述如何根据索引下标定位数组元素?

    static {
        // 获取此数组的每个成员的内存偏移量(就是每个对象占内存大小),此处整形返回4
        int scale = unsafe.arrayIndexScale(int[].class);
        if ((scale & (scale - 1)) != 0) // scale必须是2的乘幂
            throw new Error("data type scale not a power of two");
        // numberOfLeadingZeros返回scale位数第一个遇到1的0的个数
        // 0000 0000 0000 0000 0000 0000 0000 0100,此处是29
        shift = 31 - Integer.numberOfLeadingZeros(scale);
    }
    
    // 下标i的元素内存偏移地址
    private static long byteOffset(int i) {
        // 由于数组使用的是一块连续的内存空间,可以这么计算
        // base = unsafe.arrayBaseOffset(int[].class),获取首个元素的偏移地址
        // 可以证明 i * scale + base == i << shift + base
        return ((long) i << shift) + base;
    }
    
    // 延迟设置,设置的值不保证立刻刷入主内存,被其他线程可见,其它线程可能看到值会在稍许的延迟之后
    public final void lazySet(int i, int newValue) {
        unsafe.putOrderedInt(array, checkedByteOffset(i), newValue);  
    //使用Unsafe.putOrderedObject方法,这个方法在对低延迟代码是很有用的,它能够实现非堵塞的写入,
    //这些写入不会被Java的JIT重新排序指令(instruction reordering),这样它使用快速的存储-存储(store- 
    //store) barrier, 而不是较慢的存储-加载(store-load) barrier, 后者总是用在volatile的写操作上,
    //这种性能提升是有代价的,也就是写后结果并不会被其他线程看到,甚至是自己的线程,通常是几纳秒后
    //被其他线程看到,这个时间比较短,所以代价可以忍受。总之牺牲可见性来提升性能。
    //关于这一点,可以参考:https://www.cnblogs.com/chenyangyao/p/5269622.html
    }

    引用原子类,AtomicReference略过,主要介绍AtomicReferenceFieldUpdater和AtomicMarkableReference!
    一、AtomicReferenceFieldUpdater特征
    ①基于反射
    ②AtomicReferenceFieldUpdater是抽象类,通过静态方法newUpdater(Class<U> tclass, Class<W> vclass, String fieldName)来创建实例对象,实例对象类型是AtomicReferenceFieldUpdater的静态内部类AtomicReferenceFieldUpdaterImpl。其中参数

    tclass目标对象的类型
    vclass目标字段的类型
    fieldName目标字段名

    ③fieldName必须是volatile,类型修饰符不能是private、static,如果目标对象类型同AtomicReferenceFieldUpdater所在使用类是同包或者目标对象类型在AtomicReferenceFieldUpdater所在使用类中,则fieldName可以是默认修饰符或者protected。不同包必须是public。

    public class AtomicTest {
        public static void main(String[] args) {
            AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(Dog.class, String.class, "name");
            Dog dog = new Dog();
            updater.compareAndSet(dog, dog.name, "white dog");
            System.out.println(dog.name);
        }
    }
    class Dog {
        volatile String name = "black dog";
    }

    二、AtomicMarkableReference与AtomicStampedReference区别
    AtomicMarkableReference不关心变量是否被更改过几次,只是单纯想知道是否被更改过,通过boolean值来标识是否被更改,而AtomicStampedReference通过int值来标识变量被更改的次数,可以解决ABA问题。其它功能都相似。
    AtomicMarkableReference内部维护了一个private volatile Pair<V> pair,用于存储引用对象和boolean标识。

    private static class Pair<T> {
            final T reference;
            final boolean mark;
            private Pair(T reference, boolean mark) {
                this.reference = reference;
                this.mark = mark;
            }
            static <T> Pair<T> of(T reference, boolean mark) {
                return new Pair<T>(reference, mark);
            }
        }
    
    // 主要方法如下
    public V get(boolean[] markHolder) {
            Pair<V> pair = this.pair;
            markHolder[0] = pair.mark; 
            return pair.reference;
        }
    
    public boolean compareAndSet(V expectedReference, V newReference,
                                     boolean expectedMark,boolean newMark) {
            Pair<V> current = pair;
            // 如果预期值和新值一致返回true,否则更新为新的Pair
            return
                expectedReference == current.reference &&
                expectedMark == current.mark &&
                ((newReference == current.reference &&
                  newMark == current.mark) ||
                 casPair(current, Pair.of(newReference, newMark)));
        }
    
    public boolean attemptMark(V expectedReference, boolean newMark) {
            Pair<V> current = pair;
            return
                expectedReference == current.reference &&
                (newMark == current.mark ||
                 casPair(current, Pair.of(expectedReference, newMark)));
        }

    字段原子类,以AtomicIntegerFieldUpdater为例,AtomicLongFieldUpdater类似AtomicIntegerFieldUpdater,源码比较简单略过
    AtomicIntegerFieldUpdater特征
    ①字段必须是volatile类型的,在线程之间共享变量时保证立即可见
    ②调用者拥有操作对象字段的权限,那么就可以反射进行原子操作
    ③对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段 
    ④只能是实例变量,不能是类变量,也就是说不能加static关键字 
    ⑤只能是可修改变量,不能使final变量,因为final的语义就是不可修改 
    ⑥对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater

    // 只验证上述第3点
    public class Parent {
        public volatile int age = 50;
    }
    
    public class Son extends Parent{
        // public volatile int age = 30;
    }
    
    public class AtomicTest {
        public static void main(String[] args) {
            Son son = new Son();
            System.out.println(son.age); // 此处可以访问父类age,打印50
            AtomicIntegerFieldUpdater aifU = AtomicIntegerFieldUpdater.newUpdater(Son.class,"age");
            aifU.set(son, 34);
            System.out.println(son.age);
        }
    }
    
    // 运行结果如下:
    Exception in thread "main" java.lang.RuntimeException: java.lang.NoSuchFieldException: age

     

    展开全文
  • 看完了前面两篇文章,一篇是Unsafe中的CAS,另一篇是volatile,再来学习今天的Java原子类可以说是水到渠成。 1.atomic包介绍 在java.util.concurrent.atomic包下,主要分为四类: 原子更新基本类型:AtomicInteger...

    前面两篇文章,一篇文章我们介绍了Unsafe中的CAS,另一篇文章介绍了volatile语义及其实现,再来学习今天的Java原子类可以说是水到渠成。
    再简单回顾一下Unsafe中CAS——该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值;至于volatile则提供了可见性(每次读写都可以拿到最新值)和重排序限制。

    1.atomic包介绍

    java.util.concurrent.atomic包下,主要分为四类:

    • 原子更新基本类型:AtomicInteger, AtomicBoolean, AtomicLong 对底层的volatile修饰的基本类型进行CAS操作。

    • 原子更新数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray,对底层的数组进行CAS操作。

    • 原子更新引用:AtomicReference,AtomicMarkableReference,AtomicStampedReference, 对某个引用进行CAS操作。

    • 原子更新字段:AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater,对某个类的某个volatile字段进行CAS操作

    • 累加器类:DoubleAccumulator,LongAccumulator,DoubleAdder,LongAdder,多个Cell,分担CAS压力,且使用@sun.misc.Contended来确保不同Cell分布在不同的Cache line,不会发生伪共享。

    下面是该package的描述

    A small toolkit of classes that support lock-free thread-safe programming on single variables. In essence, the classes in this package extend the notion of volatile values, fields, and array elements to those that also provide an atomic conditional update operation of the form:

    一个小型工具包,支持单个变量上的无锁线程安全编程。从本质上说,该包中的类将volatile的概念延伸到那些提供原子条件更新操作的字段和数组元素:

      boolean compareAndSet(expectedValue, updateValue);
    

    This method (which varies in argument types across different classes) atomically sets a variable to the updateValue if it currently holds the expectedValue, reporting true on success. The classes in this package also contain methods to get and unconditionally set values, as well as a weaker conditional atomic update operation weakCompareAndSet described below.

    此方法 (不同类有不同的参数类型) 原子地将一个变量设置为updateValue, 如果该变量目前存的值是expectedValue,并且成功就会返回true。该包中的类还包含获取和无条件设置值的方法,以及如下所述的一个weaker版的条件原子更新操作即weakCompareAndSet。

    The specifications of these methods enable implementations to employ efficient machine-level atomic instructions that are available on contemporary processors. However on some platforms, support may entail some form of internal locking. Thus the methods are not strictly guaranteed to be non-blocking – a thread may block transiently before performing the operation.

    这些方法的规范使得利用当代处理器上可用的高效机器级原子指令成为可能(比如cmpxchg)。 但是在一些平台上,支持可能需要某种形式的内部锁**。因此这些方法不严格保证非阻塞–线程可能在执行操作之前暂时阻塞。

    Instances of classes AtomicBoolean, AtomicInteger, AtomicLong, and AtomicReference each provide access and updates to a single variable of the corresponding type. Each class also provides appropriate utility methods for that type. For example, classes AtomicLong and AtomicInteger provide atomic increment methods. One application is to generate sequence numbers, as in:

    AtomicBoolean, AtomicInteger, AtomicLong 和 AtomicReference, 每个都提供对相应类型单个变量的访问和更新。每个类也为该类型提供了适当的工具方法。比如:AtomicLong和AtomicInteger就提供了原子的 increment 方法。一个应用程序可以按照如下方式生成序列号:

     class Sequencer {
       private final AtomicLong sequenceNumber
         = new AtomicLong(0);
       public long next() {
         return sequenceNumber.getAndIncrement();
       }
     }
    

    It is straightforward to define new utility functions that, like getAndIncrement, apply a function to a value atomically. For example, given some transformation

    定义新的工具方法是直接了当的,比如 getAndIncrement ,原子地将一个方法应用到一个数值上去。比如,给定一个转换函数:

      long transform(long input)
    

    write your utility method as follows:

    像下面一样写的工具方法:

     long getAndTransform(AtomicLong var) {
       long prev, next;
       do {
         prev = var.get();
         next = transform(prev);
       } while (!var.compareAndSet(prev, next));
       return prev; // return next; for transformAndGet
     }
    

    The memory effects for accesses and updates of atomics generally follow the rules for volatiles, as stated in The Java Language Specification (17.4 Memory Model):
    get has the memory effects of reading a volatile variable.
    set has the memory effects of writing (assigning) a volatile variable.
    lazySet has the memory effects of writing (assigning) a volatile variable except that it permits reorderings with subsequent (but not previous) memory actions that do not themselves impose reordering constraints with ordinary non-volatile writes. Among other usage contexts, lazySet may apply when nulling out, for the sake of garbage collection, a reference that is never accessed again.
    weakCompareAndSet atomically reads and conditionally writes a variable but does not create any happens-before orderings, so provides no guarantees with respect to previous or subsequent reads and writes of any variables other than the target of the weakCompareAndSet.
    compareAndSet and all other read-and-update operations such as getAndIncrement have the memory effects of both reading and writing volatile variables.

    原子地访问和更新具有的内存效果大体遵循 volatile 规则,正如在The Java Language Specification (17.4 Memory Model)陈述的那样:

    • get 具有读一个volatile变量的内存效果
    • set 具有写一个volatile变量的内存效果
    • lazySet具有写入(分配)volatile变量的内存效果,除了它允许对后续(但不是先前)的内存操作进行重排序,而这些内存操作本身不会对普通的non-volatile写入施加强加重排序约束。 在其他使用上下文中,为避免垃圾回收,在清空时可以使用lazySet,该引用不再被访问。
    • weakCompareAndSet原子方式读取和有条件地写入一个变量,但不会产生任何事先的排序,因此对于weakCompareAndSet以外的任何变量的前一次或后续读取和写入都不提供任何weakCompareAndSet 。
    • compareAndSet和所有其他读取和更新操作(如getAndIncrement)具有读写volatile变量的内存效果。

    In addition to classes representing single values, this package contains Updater classes that can be used to obtain compareAndSet operations on any selected volatile field of any selected class. AtomicReferenceFieldUpdater, AtomicIntegerFieldUpdater, and AtomicLongFieldUpdater are reflection-based utilities that provide access to the associated field types. These are mainly of use in atomic data structures in which several volatile fields of the same node (for example, the links of a tree node) are independently subject to atomic updates. These classes enable greater flexibility in how and when to use atomic updates, at the expense of more awkward reflection-based setup, less convenient usage, and weaker guarantees.

    除了表示单个值的类之外,此程序包还包含Updater类,这些类可用于对任何选定类的任何选定volatile字段执行compareAndSet操作。 AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater和AtomicLongFieldUpdater是基于反射的实用程序,它们提供对关联字段类型的访问。这些主要用于原子数据结构,在该数据结构中,同一节点的几个volatile字段(例如,树节点的链接)将独立进行原子更新。这些类在如何以及何时使用原子更新方面提供了更大的灵活性,但代价是基于反射的设置更加笨拙,使用不方便且保证较弱。

    The AtomicIntegerArray, AtomicLongArray, and AtomicReferenceArray classes further extend atomic operation support to arrays of these types. These classes are also notable in providing volatile access semantics for their array elements, which is not supported for ordinary arrays.

    AtomicIntegerArray,AtomicLongArray和AtomicReferenceArray类进一步将原子操作支持扩展到这些类型的数组这些类还为它们的数组元素提供volatile的访问语义,而普通数组不支持这些语义

    The atomic classes also support method weakCompareAndSet, which has limited applicability. On some platforms, the weak version may be more efficient than compareAndSet in the normal case, but differs in that any given invocation of the weakCompareAndSet method may return false spuriously (that is, for no apparent reason). A false return means only that the operation may be retried if desired, relying on the guarantee that repeated invocation when the variable holds expectedValue and no other thread is also attempting to set the variable will eventually succeed. (Such spurious failures may for example be due to memory contention effects that are unrelated to whether the expected and current values are equal.) Additionally weakCompareAndSet does not provide ordering guarantees that are usually needed for synchronization control. However, the method may be useful for updating counters and statistics when such updates are unrelated to the other happens-before orderings of a program. When a thread sees an update to an atomic variable caused by a weakCompareAndSet, it does not necessarily see updates to any other variables that occurred before the weakCompareAndSet. This may be acceptable when, for example, updating performance statistics, but rarely otherwise.

    原子类还支持方法weakCompareAndSet,该方法的适用性有限。在某些平台上,弱版本在正常情况下可能比compareAndSet更有效,但不同之处在于,对weakCompareAndSet方法的任何给定调用都可能虚假地返回false(即,没有明显的原因)。返回false仅意味着可以根据需要保证重试该操作,如果该变量持有expectedValue且没有其他线程尝试设置该变量,那么重复调用最终将成功。 (例如,此类虚假故障可能是由于与预期值和当前值是否相等无关的内存争用效应引起的。)。此外,weakCompareAndSet不提供同步控制通常需要的排序保证。但是,该方法对于更新计数器和统计信息可能有用, 由于此类更新与程序的其他happens-before顺序无关。当线程看到由weakCompareAndSet引起的原子变量更新时,它不一定会看到对weakCompareAndSet之前发生的任何其他变量的更新。例如,在更新性能统计信息时,这可能是可以接受的,但很少如此。

    The AtomicMarkableReference class associates a single boolean with a reference. For example, this bit might be used inside a data structure to mean that the object being referenced has logically been deleted. The AtomicStampedReference class associates an integer value with a reference. This may be used for example, to represent version numbers corresponding to series of updates.

    AtomicMarkableReference类将单个布尔值与引用关联。例如,此位可能在数据结构内使用,表示所引用的对象在逻辑上已被删除。 AtomicStampedReference类将整数值与引用关联。例如,这可以用于表示与一系列更新相对应的版本号。

    Atomic classes are designed primarily as building blocks for implementing non-blocking data structures and related infrastructure classes. The compareAndSet method is not a general replacement for locking. It applies only when critical updates for an object are confined to a single variable.

    原子类主要设计为构建块,用于实现非阻塞数据结构和相关的基础结构类。 compareAndSet方法不是锁的一般替代方法。它仅在将对象的关键更新限制在单个变量中时适用。

    Atomic classes are not general purpose replacements for java.lang.Integer and related classes. They do not define methods such as equals, hashCode and compareTo. (Because atomic variables are expected to be mutated, they are poor choices for hash table keys.) Additionally, classes are provided only for those types that are commonly useful in intended applications. For example, there is no atomic class for representing byte. In those infrequent cases where you would like to do so, you can use an AtomicInteger to hold byte values, and cast appropriately. You can also hold floats using Float.floatToRawIntBits(float) and Float.intBitsToFloat(int) conversions, and doubles using Double.doubleToRawLongBits(double) and Double.longBitsToDouble(long) conversions.

    原子类不是java.lang.Integer和相关类的通用替代品。他们没有定义诸如equals,hashCode和compareTo之类的方法。 (由于原子变量预期会发生改变,因此它们对于哈希表键而言是较差的选择。)此外,仅为那些在预期应用程序中通常有用的类型提供了原子类。比方说,没有用于表示字节的原子类(因为一般用不到)。如果你不希望这样做,可以使用AtomicInteger来保存字节值,并进行适当的转换。你还可以使用Float.floatToRawIntBits(float)和Float.intBitsToFloat(int)转换来持有float,并使用Double.doubleToRawLongBits(double)和Double.longBitsToDouble(long)转换来持有double。

    2.使用示例及解析

    2.1 原子更新基本类型

    2.1.1 使用

    AtomicInteger ai = new AtomicInteger(0);
    Runnable r = new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 100000; i++) {
                ai.getAndIncrement();
            }
        }
    };
    new Thread(r).start();
    new Thread(r).start();
    //等待任务完成
    Thread.sleep(10000);
    System.out.println(ai.get()); //20000
    

    2.1.2 源码

    首先是一些字段的声明,

    // 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;
    

    接着可以看到getAndIncrement,调用了Unsafe类中getAndAddInt方法

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

    接着看到Unsafe中的getAndAddInt方法

    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    
        return var5;
    }
    

    接着通过这个链接,我们来看下compareAndSwapInt和getIntVolatile的描述

    public final native boolean compareAndSwapInt(Object o,
        long offset, //Java变量在内存中的偏移量
        int expected, //期望值
        int x)
    

    o代表Java对象,offset表示要设置的字段在该对象中的内存偏移量。如果该偏移量处存值为expected,那么就将偏移量处存值更新为x,并返回true;其他情况返回false。

     public native int     getIntVolatile(Object o, long offset);
    

    volatile版本的getInt,也就是从对象o,偏移量为offset的内存地址,利用volatile语义取出对应字段的最新值。
    所以这时候我们返回看getAndAddInt的实现,就发现,do-whilie循环中会利用volatile语义取到字段 private volatile int value的最新值var5,然后再下一步尝试CAS,如果成功就返回var5; 否则,如果有其他线程CAS成功,则进入循环重新在走一遍。

    关于Unsafe.compareAndSwapInt又是如何实现的,由于该方法是native的,这就涉及到JVM了,请参见之前的Unsafe文章说明

    2.2 原子更新数组

    下面以AtomicIntegerArray举例

    2.2.1使用示例

    int[] value = new int[]{1, 2};
    AtomicIntegerArray aia = new AtomicIntegerArray(value);
    aia.getAndSet(0, 3);
    System.out.println(aia.get(0)); //3
    System.out.println(value[0]); //1
    

    2.2.2 源码

    /**
     * Atomically sets the element at position {@code i} to the given
     * value and returns the old value.
     *
     * @param i the index
     * @param newValue the new value
     * @return the previous value
     */
    public final int getAndSet(int i, int newValue) {
        return unsafe.getAndSetInt(array, checkedByteOffset(i), newValue);
    }
    

    原子第设置数组下标为i的元素值为newValue,并且返回之前的值。
    首先我们来看下getAndSetInt的实现

    public final int getAndSetInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var4));
    
        return var5;
    }
    

    与上面我们说的getAndAddInt的实现基本一致,就是获取最新值,然后尝试CAS,成功就返回,失败就再来一遍。

    另外checkedByteOffset是用来获取指定下标的内存偏移量的,相关代码如下:

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final int base = unsafe.arrayBaseOffset(int[].class);
    private static final int shift;
    private final int[] array;
    
    static {
        int scale = unsafe.arrayIndexScale(int[].class); //获取比例因子, 该因子用于给存储分配中特定数组类的元素寻址
        if ((scale & (scale - 1)) != 0) 
            throw new Error("data type scale not a power of two");
        shift = 31 - Integer.numberOfLeadingZeros(scale);
    }
    
    private static long byteOffset(int i) {
        return ((long) i << shift) + base;
    }
    

    2.3 原子更新引用类型:

    这里以AtomicStampedReference举例,AtomicMarkableReference和他的实现类似。

    2.3.1 使用示例

    String s = "hello";
    AtomicStampedReference<String> atomicStampedReference = new AtomicStampedReference<String>(s ,0);
    Runnable r = new Runnable() {
        @Override
        public void run() {
            boolean res = atomicStampedReference.compareAndSet("hello", "world", 0, 1);
            if(res){
                System.out.println(Thread.currentThread().getName()+" win ");
            }else{
                System.out.println(Thread.currentThread().getName()+" fail ");
            }
        }
    };
    new Thread(r).start();
    new Thread(r).start();
    

    2.3.2 源码

    正如上面官方包描述所说的那样,AtomicStampedReference类将整数值与引用关联。在它的实现类中就定义了一个静态内部类Pair, 一个表示引用,一个表示整数值,两个绑定在一起。

    private static class Pair<T> {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }
    

    我们再来看看使用示例中AtomicStampedReference.compareAndSet的实现

    /**
     * Atomically sets the value of both the reference and stamp
     * to the given update values if the
     * current reference is {@code ==} to the expected reference
     * and the current stamp is equal to the expected stamp.
     *
     * @param expectedReference the expected value of the reference
     * @param newReference the new value for the reference
     * @param expectedStamp the expected value of the stamp
     * @param newStamp the new value for the stamp
     * @return {@code true} if successful
     */
    public boolean compareAndSet(V   expectedReference, //期望的引用
                                 V   newReference, //新的引用
                                 int expectedStamp, //期望的stamp
                                 int newStamp) //新的stamp{ 
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }
    

    如果当前引用==expectedReference,当前stamp等于期望stamp,就原子地将引用和stamp设置为newReference和newStamp。
    下面我们具体看下casPair的实现。

    private volatile Pair<V> pair;
    private static final long pairOffset =
        objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);
    
    private boolean casPair(Pair<V> cmp, Pair<V> val) {
        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
    }
    

    这里的compareAndSwapObject 和上面说到的compareAndSwapInt的语义基本一致,也就是如果AtomicStampedReference对象pairOffset偏移量处存的数据,与cmp相等,则将该偏移量的值设置为新值val,并返回true;其他情况则返回false。

    2.4 原子更新字段

    这里以AtomicReferenceFieldUpdater为例子

    2.4.1 使用示例

    public class AtomicReferenceFieldUpdaterTest {
        public static void main(String[] args) {
            City city = new City(12345, "Shanghai");
            User user = new User("YellowStar5", city);
            AtomicReferenceFieldUpdater<User, City> fieldUpdater = AtomicReferenceFieldUpdater.newUpdater(User.class, City.class, "city");
            City city2 = new City(678910, "Hangzhou");
            fieldUpdater.compareAndSet(user, city, city2);
            System.out.println(fieldUpdater.get(user));
        }
    
        static class User {
            private final String name;
            /**
             * 访问等级:package 或者public才行
             * field为基本类型或Void不行
             */
    
            volatile City city;
    
            public User(String name, City city) {
                this.name = name;
                this.city = city;
            }
        }
    
        static class City {
            private int id;
            private String name;
    
            public City(int id, String name) {
                this.id = id;
                this.name = name;
            }
    
            @Override
            public String toString() {
                return "City{" +
                        "id=" + id +
                        ", name='" + name + '\'' +
                        '}';
            }
        }
    }
    

    2.4.2 源码

    public boolean compareAndSet(T obj, V expect, V update) {
        if (obj == null || obj.getClass() != tclass || cclass != null ||
            (update != null && vclass != null &&
             vclass != update.getClass()))
            updateCheck(obj, update);
        return unsafe.compareAndSwapObject(obj, offset, expect, update);
    }
    

    compareAndSwapObject 在上一节中已经讲过。这里不再赘述。
    下面我们来看下,AtomicReferenceFieldUpdater.newUpdater(User.class, City.class, “city”);这个构造函数

    AtomicReferenceFieldUpdaterImpl(final Class<T> tclass,
                                    final Class<V> vclass,
                                    final String fieldName,
                                    final Class<?> caller) {
        final Field field;
        final Class<?> fieldClass;
        final int modifiers;
        try {
            field = AccessController.doPrivileged(
                new PrivilegedExceptionAction<Field>() {
                    public Field run() throws NoSuchFieldException {
                        return tclass.getDeclaredField(fieldName);
                    }
                });
            modifiers = field.getModifiers();
            sun.reflect.misc.ReflectUtil.ensureMemberAccess(
                caller, tclass, null, modifiers);
            ClassLoader cl = tclass.getClassLoader();
            ClassLoader ccl = caller.getClassLoader();
            if ((ccl != null) && (ccl != cl) &&
                ((cl == null) || !isAncestor(cl, ccl))) {
              //确保该字段得是package的或public的。
              sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
            }
            fieldClass = field.getType();
        } catch (PrivilegedActionException pae) {
            throw new RuntimeException(pae.getException());
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    
        if (vclass != fieldClass)
            throw new ClassCastException();
        if (vclass.isPrimitive())
            //必须非基本类型。
            throw new IllegalArgumentException("Must be reference type");
    
        if (!Modifier.isVolatile(modifiers))
            //必须volatile类型。
            throw new IllegalArgumentException("Must be volatile type");
    
        this.cclass = (Modifier.isProtected(modifiers) &&
                       caller != tclass) ? caller : null;
        this.tclass = tclass;
        if (vclass == Object.class)
            this.vclass = null;
        else
            this.vclass = vclass;
        offset = unsafe.objectFieldOffset(field);
    }
    

    2.5 累加器类

    主要包括DoubleAccumulator,LongAccumulator,DoubleAdder,LongAdder。

    2.5.1 使用

    LongAdder adder = new LongAdder();
    Runnable r = new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 100000; i++) {
                adder.add(i);
            }
        }
    };
    Thread t1 = new Thread(r);
    Thread t2 = new Thread(r);
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(adder.longValue());
    

    2.5.2 原理

    我们通过上面的分析,发现之前的原子类CAS操作的基本都是同一个volatile variable(某个基本类型或者引用),并且如果此时有多个线程同时操作该variable,就会引起争用(contention)。

    为了解决这个问题,减少争用,这些累加器类就将CAS 扩展到一个Cell数组,每次都根据当前线程获取到对应的Cell来进行CAS操作。

    下面我们可以看下Cell类的定义,@sun.misc.Contended注解确保不会出现伪共享,简单来说就是两个及两个以上Cell对象不会被放到同一个缓存行内(Cache line),不会造成每个CPU Core的L1 Cache里面的cache line 轮流失效。更多请参考 false-sharingJava8使用@sun.misc.Contended避免伪共享

    关于LongAccumulator的讲解,还可参考犀利豆的文章

    @sun.misc.Contended static final class Cell {
        volatile long value;
        Cell(long x) { value = x; }
        final boolean cas(long cmp, long val) {
            return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
        }
    
        // Unsafe mechanics
        private static final sun.misc.Unsafe UNSAFE;
        private static final long valueOffset;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> ak = Cell.class;AtomicMarkableReference
                valueOffset = UNSAFE.objectFieldOffset
                    (ak.getDeclaredField("value"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }
    

    但要注意,下面的sum函数获取的是只是cells的快照,在求和过程中cells发生的更新就不会反映在结果里了。

    /*
     *返回当前总和。 返回的值不是原子快照。 在没有并发更新的情况下调用会返回准确的结果,但是在计算sum时发生的并发更新可能不会被合并。
     *
     * @return the sum
     */
    public long sum() {
        Cell[] as = cells; Cell a;
        long sum = base;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }
    

    3.ABA问题

    首先来看ABA的定义

    In multithreaded computing, the ABA problem occurs during synchronization, when a location is read twice, has the same value for both reads, and “value is the same” is used to indicate “nothing has changed”. However, another thread can execute between the two reads and change the value, do other work, then change the value back, thus fooling the first thread into thinking “nothing has changed” even though the second thread did work that violates that assumption.

    在多线程计算中,在同步过程中会发生ABA问题,当一个位置被读取两次,两次读取具有相同的值,并且“值相同”用于指示“什么都没有改变”。但是,另一个线程可以在两次读取之间执行并更改值,执行其他工作,然后将值改回,因此,即使第二个线程的工作违反了该假设,也使第一个线程认为“什么都没有改变”。

    The ABA problem occurs when multiple threads (or processes) accessing shared data interleave. Below is the sequence of events that will result in the ABA problem:

    当多个线程(或进程)访问共享数据时,会发生ABA问题。以下是将导致ABA问题的事件序列:

    • Process P1 从共享内存读取值A,
    • P1 被抢占,允许进程P2运行,
    • P2 在被强占之前将共享内存值A改成值B,然后再改回值A,
    • P1 又开始执行,发现共享内存值没有更新并继续。

    相似地使用AtomicInteger也有类似的问题,假如存在两个线程按下面的序列执行

    • 线程T1从AtomicInteger读取值A,
    • 线程T1被切换出去,允许线程T2运行,
    • T2 在被切换出去之前将AtomicInteger值A改成值B,然后再改回值A,
    • T1 又开始执行,发现AtomicInteger没有更新并继续。

    这时候就可以用AtomicStampedReference来解决ABA问题了。

    private static class Pair<T> {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }
    

    由于每个Pair都关联了一个stamp,只需要每次设置值reference的时候同时更新一下stamp(比如加1),即可解决ABA问题。当然ABA的解决方式不只这一种,只不过Java里面选用了这一种,具体请参见维基百科

    4. 总结

    一句话,其实atomic包, 主要就是利用volatile提供的内存语义,和Unsafe提供的CAS操作实现的。

    分类相关类原理使用场景
    原子更新基本类型AtomicInteger,AtomicBoolean,AtomicLong对volatile 修饰的int, long等基本类型进行CAS操作在多线程场景下取代基本类型
    原子更新数组AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray对final修饰的int[], long[],Object[]数组中的元素进行CAS操作对于数组的并发操作
    原子更新引用AtomicReference,AtomicMarkableReference,AtomicStampedReference对底层的某个引用进行CAS操作。AtomicMarkableReference类将单个布尔值与引用关联, AtomicStampedReference类将整数值与引用关联。AtomicMarkableReference 利用关联的布尔值表示所引用的对象在逻辑上是否被删除。AtomicStampedReference 利用关联的整数值来表示版本号,每次更新就加1。这样可以解决CAS的ABA问题。
    原子更新字段AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater对某个类的某个volatile字段进行CAS操作对某个类的某个volatile字段进行CAS操作
    累加器类DoubleAccumulator,LongAccumulator,DoubleAdder,LongAdder多个Cell,分担CAS压力,且使用@sun.misc.Contended来确保不同Cell分布在不同的Cache line,不会发生伪共享。适用于统计信息,允许返回结果为过去的某个快照,也就是非最新值。

    另外,使用示例写的都极其简单,如果需要使用,建议先读下对应的javadoc。

    展开全文
  • 引言   Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作。...  因为变量的类型有很多种,所以在Atomic包里一共提供了13个,可分为4种类型的原子更新方式...

    引言

      Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作。原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞。

      因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,可分为4种类型的原子更新方式,分别是

    • 原子更新基本类型
    • 原子更新数组
    • 原子更新引用
    • 原子更新属性(字段)

    Atomic包里的类基本都是使用Unsafe实现的包装类。

    一、基本类型的原子操作类

    Atomic 包提供了以下3个基本类型的原子操作类:

    • AtomicBoolean: 原子更新布尔类型;
    • AtomicInteger: 原子更新整形;
    • AtomicLong: 原子更新长整形;

    以上3个类提供的方法几乎一模一样,所以本文仅以AtomicInteger为例,常用方法如下:

    • int addAndGet(int delta): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果
    • boolean compareAndSet(int expect,int update): 如果输入的数值等于预期值,则以原子方式将该值设置为输入的值
    • int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值,相当于 (i++) 的形式。
    • void lazySet(int newValue): JDK6所增加,最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。关于该方法的更多信息可以参考并发编程网翻译的一篇文章《AtomicLong.lazySet是如何工作的?》,文章地址是“http://ifeve.com/howdoes-atomiclong-lazyset-work/”
    • int getAndSet(int newValue): 以原子方式设置为newValue的值,并返回旧值。

    @ Example1AtomicInteger 使用例子

    import java.util.concurrent.atomic.AtomicInteger;
    
    public class AtomicIntegerTest {
    
        static AtomicInteger ai = new AtomicInteger(1);
    
        public static void main(String[] args) {
            System.out.println(ai.getAndIncrement());
            System.out.println(ai.get());
        }
    }

    运行结果:

    1
    2

    其余的基本类的原子操作类的解决方案

      java 的基本类型一共有8个,然而JDK却只提供了三个,如何原子的更新其他的基本类型呢?特别是常用的charfloatdouble。Atomic包里的类基本都是使用Unsafe实现的,让我们一起看一下Unsafe的源码.

     /**
         * 如果当前数值是expected,则原子的将Java变量更新成x
         * @return 如果更新成功则返回true
         */
        public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
        
        public final native boolean compareAndSwapInt(Object o, long offset, int expected,int x);
    
        public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
    

      通过源码,可以发现Unsafe只提供了3种CAS方法,那么 AtomicBoolean 是如何实现的呢,我们再来看一下 AtomicBoolean 的源码。只给出关键部分

    public class AtomicBoolean implements java.io.Serializable {
    //存储值:1,0;1代表 true,0 代表 false 
    private volatile int value;
    //Unsafe 对象
     private static final Unsafe unsafe = Unsafe.getUnsafe();
    
    
    public final boolean compareAndSet(boolean expect, boolean update) {
            //boolean 类型 转换成 整形,true 对应 1,false 对应 0;
            int e = expect ? 1 : 0;
            int u = update ? 1 : 0;
            return unsafe.compareAndSwapInt(this, valueOffset, e, u);
        }
    
    public final void set(boolean newValue) {
            value = newValue ? 1 : 0
        }
    
    public final boolean get() {
            return value != 0;
        }
    
    //.......
    }

      从AtomicBoolean 的源码可以看出,它是先把 boolean 的值转换成整型来存储,再使用compareAndSwapInt进行CAS。所以其他基本类型的原子类型也可参照这个思路来实现。

      思路有了,接下来考虑怎么实现。bytecharint 的转换很容易,他们的原子操作类就很容易实现了。那floatdouble 要怎样才能以整形或者长整形来存储值呢?
      Float 类中 提供了static两个方法:通过 Static int floatToIntBits(float value) (根据 IEEE 754 浮点“单一格式”位布局,返回指定浮点值的表示形式)方法,便可以将float 的值以整形的方式存储下来。如果要取值,则通过 static float intBitsToFloat(int bits)方法,将存储下来的整形的浮点值转换回原来的float值。
      同理,Double 类也提供了类似的两个方法:static long doubleToLongBits(double value) 产生long类型的浮点值。static double longBitsToDouble(long bits)long的浮点值转换回double值。

    下面是 AtomicDouble 的实现例子:

    @ Example2AtomicDouble 的实现例子

    import java.util.concurrent.atomic.AtomicLong;
    
    public class AtomicDouble extends Number {
    
        private AtomicLong bits;
        
        public AtomicDouble() {
            this(0d);
        }
        
        public AtomicDouble(Double value){
            bits = new AtomicLong(Double.doubleToLongBits(value));
        }
    
        public final double get() {
            return Double.longBitsToDouble(bits.get());
        }
    
        public final void set(double value) {
            bits.set(Double.doubleToLongBits(value));
        }
    
        public final double addAndGet(double delta) {
            long newBits = bits.addAndGet(Double.doubleToLongBits(delta));
            return Double.longBitsToDouble(newBits);
        }
        
        public final double getAndAdd(double delta) {
            long oldBits = bits.getAndAdd(Double.doubleToLongBits(delta));
            return Double.longBitsToDouble(oldBits);
        }
    
        public final double getAndSet(double newValue) {
            long oldBits = bits.getAndSet(Double.doubleToLongBits(newValue));
            return Double.longBitsToDouble(oldBits);
        }
    
        public final boolean compareAndSet(double expect, double update) {
            return bits.compareAndSet(Double.doubleToLongBits(expect), Double.doubleToLongBits(update));
        }
    
        public final boolean weakCompareAndSet(double expect, double update) {
            return bits.weakCompareAndSet(Double.doubleToLongBits(expect), Double.doubleToLongBits(update));
        }
        
        public final void lazySet(double newValue) {
             bits.lazySet(Double.doubleToLongBits(newValue));
        }
    
        @Override
        public int intValue() { return (int) this.get(); }
        @Override
        public long longValue() { return (long) this.get(); }
        @Override
        public float floatValue() { return (float) this.get();}
        @Override
        public double doubleValue() { return this.get(); }
    }

    关于原子操作类实现其余猜想:

      除了上面的那种实现方案外,在 stackOverflow 上也发现两个原子操作类的实现方案:

    1. 利用 AtomicReference 类;如 Float 的原子操作类可以是 AtomicReference<Float>。不可,经过测试,这种方法是不行的,因为像compareAndSet这类方法比较的是对象的内存地址,而不会使用equal()方法进行比较。
    2. 使用JDK1.8新增的方法:DoubleAdderDoubleAccumulator。没去深入了解,粗略看了下,这两个类都是适合于“多写少读”的情况。

    二、数组的原子操作类

    通过原子的方式更新数组里的某个元素,Atomic包提供了以下3个类:

    • AtomicIntegerArray: 原子更新整型数组里的元素
    • AtomicLongArray: 原子更新长整型数组里的元素
    • AtomicReferenceArray: 原子更新引用类型数组里的元素

    这几个类的方法几乎一样,以AtomicIntegerArray为例,AtomicIntegerArray类主要是提供原子的方式更新数组里的整型;

    构造方法:
    AtomicIntegerArray(int length): 创建给定长度的新 AtomicIntegerArray。
    AtomicIntegerArray(int[] array): 创建与给定数组具有相同长度的新 AtomicIntegerArray,并从给定数组复制其所有元素。

    其常用方法:
    int addAndGet(int i, int delta): 以原子方式将输入值与数组中索引i的元素相加。
    boolean compareAndSet(int i, int expect, int update): 如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。

    @ Example3AtomicIntegerArray 的使用例子

    public class AtomicIntegerArrayTest {
    
        static int[] value = new int[] { 1, 2 };
    
        static AtomicIntegerArray ai = new AtomicIntegerArray(value);
    
        public static void main(String[] args) {
            ai.getAndSet(0, 3);
            System.out.println(ai.get(0));
                    System.out.println(value[0]);
        }
    }

    运行结果:

    3
    1

    AtomicIntegerArray类需要注意的是 ,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响到传入的数组。


    三、引用类型的原子操作类

     基本类型的原子操作类只能更新基本类型的值,不能更新引用类型的对象引用。Atomic包提供了以下三个类,可以原子方式更新的对象引用。

    • AtomicReference: 原子更新引用类型。
    • AtomicStampedReference: 原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号,可以解决使用CAS进行原子更新时,可能出现的ABA问题。
    • AtomicMarkableReference: 原子更新带有标记位的引用类型。可以原子的更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef, boolean initialMark)

    1. AtomicReference 介绍

    构造方法

    AtomicReference(): 使用 null 初始值创建新的 AtomicReference
    AtomicReference(V initialValue): 使用给定的初始值创建新的 AtomicReference

    常用的方法: 与前面介绍的类差不多,get()set(V newValue)compareAndSet(V expect, V update)getAndSet(V newValue);

    @ Example4AtomicReference 的使用例子

    public class AtomicReferenceTest {
    
        public static AtomicReference<user> atomicUserRef = new AtomicReference</user><user>();
    
        public static void main(String[] args) {
            User user = new User("conan", 15);
            atomicUserRef.set(user);
            User updateUser = new User("Shinichi", 17);
            atomicUserRef.compareAndSet(user, updateUser);
            System.out.println(atomicUserRef.get().getName());
            System.out.println(atomicUserRef.get().getOld());
        }
    
        static class User {
            private String name;
            private int old;
    
            public User(String name, int old) {
                this.name = name;
                this.old = old;
            }
    
            public String getName() {
                return name;
            }
    
            public int getOld() {
                return old;
            }
        }
    }

    运行结果:

    Shinichi
    17

    2. AtomicMarkableReference 与 AtomicStampedReference 介绍

      这两个类的作用与 AtomicReference 的作用非常相似,都是原子更新引用类型。但他们还有一个作用:能解决CAS过程中的ABA问题

    什么是ABA问题?

      ABA的问题就是:在多线程的环境下,线程1将A的值赋给了B,然后B的值又重新赋值了A。在这个过程中,A已经被修改了一次了,但是线程2不知道,在进行CAS时,认为A的值没有被修改过,所以就进行修改。当然,如果只对结果敏感,而对修改的次数不敏感,那么这个问题就无所谓了。

    AtomicStampedReference 和 AtomicMarkableReference 是怎么解决ABA问题的?

      这两个类的解决方案也很简答,就是多维护了一个标志位记录修改的状态 或者 维护一个版本号记录修改的次数,然后进行CAS时,也会比较标志位或者版本号。简单看一下源码吧。

    AtomicMarkableReference 用的是标志位mark(布尔类型)来标志修改的状态,下面是关键部分源码:

    //构造方法,initialRef 是 初始的引用类型,initialMark 是初始的标志位
    public AtomicMarkableReference(V initialRef,  boolean initialMark);
    
    //CAS 不仅要比较引用类型,还要比较标志位
    public boolean compareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark) {
            Pair<V> current = pair;
            return
                expectedReference == current.reference &&
                expectedMark == current.mark &&
                ((newReference == current.reference &&
                  newMark == current.mark) ||
                 casPair(current, Pair.of(newReference, newMark)));
     }

    AtomicStampedReference 用的则是版本号stamp(整形int) 来记录修改的次数,下面是关键部分的源代码:

    //构造方法,初始引用类型 和 初始版本号
     public AtomicStampedReference(V initialRef, int initialStamp);
    
    //CAS 不仅要比较引用类型,还要比较版本号
      public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) {
            Pair<V> current = pair;
            return
                expectedReference == current.reference &&
                expectedStamp == current.stamp &&
                ((newReference == current.reference &&
                  newStamp == current.stamp) ||
                 casPair(current, Pair.of(newReference, newStamp)));
        }

    @ Example5AtomicStampedReference 的解决ABA问题的例子

    看一个AtomicStampedReference的例子。当需要执行一次更新当前用户的操作,这里故意将更新的用户就是当前用户,也就是更新后对象引用是没有变化的。线程A、线程B 都接到这个任务,但只能执行一次,意味着得有一个线程更新失败。User类的代码请查看上一个例子。

    public class AtomicStampedReferenceTest {
    
        public static void main(String[] args) throws InterruptedException {
            User user = new User("小赫",25);
            //初始版本为1
            int stamp = 1;
            
            //设置当前用户是  小赫
            AtomicStampedReference<User> currentAtomicUser = new AtomicStampedReference<User>(user, stamp);
            
            //更新当前用户,更新的目标用户就是当前用户
            UpdateUserTask task = new UpdateUserTask(stamp, currentAtomicUser, user, user);
            //线程A,线程B执行相同的更新任务,但更新操作只需要执行一次。
            Thread threadA = new Thread(task,"ThreadA");
            Thread threadB = new Thread(task,"ThreadB");
            threadA.start();
            threadB.start();
        }
    }
    
    class UpdateUserTask implements Runnable{
    
        private int stamp;
        private AtomicStampedReference<User> currentAtomicUser;
        private User newUser;
        private User oldUser;
        
        public UpdateUserTask(int stamp, AtomicStampedReference<User> currentAtomicUser,User newUser,User oldUser) {
            this.stamp = stamp;
            this.currentAtomicUser = currentAtomicUser;
            this.newUser = newUser;
            this.oldUser = oldUser;
        }
    
        @Override
        public void run() {
            //更新当前用户,版本号加一
            boolean b = currentAtomicUser.compareAndSet(oldUser, newUser, stamp, stamp+1);
            if(b){//更新执行成功
                System.out.println("线程"+Thread.currentThread().getName()+": 成功执行更新操作");
            }else{//更新失败
                System.out.println("线程"+Thread.currentThread().getName()+": 当前用户是 "+ currentAtomicUser.getReference().getName()+" ,版本号已经过期,更新操作已经被其他线程完成");
            }
        }
         
     }

    运行结果:

    线程ThreadB: 当前用户是 小赫 ,版本号已经过期,更新操作已经被其他线程完成
    线程ThreadA: 成功执行更新操作

      从结果可以看出,当线程B执行任务时,尽管当前用户的对象引用没有改变,但版本号却已经改变了,线程B从而知道了更新操作已经被执行了,于是便不再执行更新。


    四、 更新字段的原子操作类

    如果我们只需要某个类里的某个字段,那么就需要使用原子更新字段类,Atomic包提供了以下三个类:

    • AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器。通过静态工厂方法创建newUpdater(Class<U> tclass, String fieldName)
    • AtomicLongFieldUpdater: 原子更新长整型字段的更新器。通过静态工厂方法创建newUpdater(Class<U> tclass, String fieldName)
    • AtomicReferenceFieldUpdater 原子更新引用类型里的字段。比前两个方法的范围要广,可以更新任意类型的字段,通过静态的工厂方法创建 newUpdater(Class<U> tclass, Class<W> vclass, String fieldName)

    原子更新字段类都是抽象类,每次使用都时候必须使用静态方法newUpdater创建一个更新器。原子更新类的字段的必须使用public volatile修饰符。以上3个类提供的方法差不多,下面给出AtomicIntegerFieldUpdater 的例子:

    @ Example6AtomicIntegerFieldUpdater 例子

    public class AtomicIntegerFieldUpdaterTest {
    
        private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater
                .newUpdater(User.class, "old");
    
        public static void main(String[] args) {
            User conan = new User("conan", 10);
            System.out.println(a.getAndIncrement(conan));
            System.out.println(a.get(conan));
        }
    
        public static class User {
            private String name;
            public volatile int old;
    
            public User(String name, int old) {
                this.name = name;
                this.old = old;
            }
    
            public String getName() {
                return name;
            }
    
            public int getOld() {
                return old;
            }
        }
    }

    运行结果:

    10
    11

    转载于:https://www.cnblogs.com/jinggod/p/8495395.html

    展开全文
  • Java原子变量

    千次阅读 2016-08-14 17:39:29
     原子变量比锁的粒度更细,量级更轻,并且对于在多处理器系统上实现高性能的并发代码来说是非常关键的。原子变量将发生竞争的范围缩小到单个变量上,这是你获得的粒度最细的情况(假设算法能够基于这种细粒度来实现...

        以下是对《java并发编程实战》一书中相关部分的总结,加深自己的印象。

        原子变量比锁的粒度更细,量级更轻,并且对于在多处理器系统上实现高性能的并发代码来说是非常关键的。原子变量将发生竞争的范围缩小到单个变量上,这是你获得的粒度最细的情况(假设算法能够基于这种细粒度来实现)。更新原子变量的快速(非竞争)路径不会比获取锁的快速路径慢,并且通常会更快,而它的慢速路径肯定比锁的慢速路径快,因为它不需要挂起或重新调度线程。在使用基于原子变量而非锁的算法中,线程在执行时更不易出现延迟,并且如果遇到竞争,也更容易恢复过来。

        原子变量类相当于一种泛化得volatile变量,能够支持原子的和有条件的读-改-写操作。AtomicInteger表示一个int类型的值,并提供了get和set方法,这些Volatile类型的int变量在读取和写入上有着相同的内存语义。它还提供了一个原子的compareAndSet方法(如果该方法成功执行,那么将实现与读取/写入一个volatile变量相同的内存效果),以及原子的增加、递增和递减等方法。

        AtomicInteger表面上非常像一个扩展的Counter类,但在发生竞争的情况下能提供更高的可伸缩性,因为它直接利用了硬件对并发的支持。

        共有12个原子变量类,可分为4组:标量类(Scalar)、更新器类、数组类、复合变量类。最常用的原子变量类就是标量类:AtomicInteger、AtomicLong、AtomicBoolean、AtomicReference。所有这些类都支持CAS,此外,AtomicInteger、AtomicLong还支持算数运算。(要想模拟其它基本类型的原子变量,可以将short或byte等类型与int类型进行转换,以及使用floatToIntBits或doubleToLongBits来转换浮点数)

        原子数组类(只支持Integer、Long和Reference)中的元素可以实现原子更新。原子数组类为数组的元素提供了volatile类型的访问语义,这是普通数组所不具备的特性--volatile类型的数组仅在数组引用上具有volatile语义,而在其元素上则没有。

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

    1.原子变量是一种“更好的volatile”

    通过CAS来维持包含多个变量的不变性条件

    public class CasNumberRange {
    
    	@Immutable
    	private static class IntPair{
    		//不变性条件:lower<=upper
    		final int lower;
    		final int 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 oldv = values.get();
    			if(i>oldv.upper){
    				throw new IllegalArgumentException(""
    						+ "Cann't set lower to"+i+">upper");
    			}
    			IntPair newv = new IntPair(i,oldv.upper);
    			if(values.compareAndSet(oldv, newv)){
    				return;
    			}
    		}
    	}
    	//对setUpper采用类似的方法
    }

    2.性能比较:锁与原子变量

        伪随机数生成器(PRNG),在PRNG中,在生成下一个随机数字时需要用到上一个数字,所以在PRNG中必须记录前一个数值并将其作为状态的一部分。

        以下给出了线程安全的PRNG的两种实现,一种使用ReentrantLock,另一种使用AtomicInteger。测试程序将反复调用它们,在每次迭代中将生成一个随机数字(在此过程中将读取并修改共享的seed状态),并执行一些仅在线程本地数据上执行的“繁忙”迭代。这种方法模拟了一些典型的操作,以及一些在共享状态以及线程本地状态上的操作。

    基于ReentrantLock实现的随机数生成器

    @ThreadSafe
    public class ReentrantLockPseudoRandom extends PseudoRandom{
    
    	private final Lock lock = new ReentrantLock(false);
    	private int seed;
    	
    	ReentrantLockPseudoRandom(int seed){
    		this.seed = seed;
    	}
    	public int nextInt(int n){
    		lock.lock();
    		try{
    			int s = seed;
    			seed = calculateNext(s);
    			int remainder = s % n;
    			return remainder >0 ? remainder : remainder+n;
    		}finally{
    			lock.unlock();
    		}
    	}
    }
    


    基于AtomicInteger实现的随机数生成器

    @ThreadSafe
    public class AtomicPseudoRandom extends PseudoRandom{
    
    	private AtomicInteger seed;
    	AtomicPseudoRandom(int seed){
    		this.seed = new AtomicInteger(seed);
    	}
    
    	public int nextInt(int n){
    		while(true){
    			int s = seed.get();
    			int nextSeed = calculateNext(s);
    			if(seed.compareAndSet(s, nextSeed)){
    				int remainder = s % n;
    				return remainder > 0 ? remainder : remainder + n;
    			}
    		}
    	}
    }
    下图给出了在每次迭代中工作量较高以及适中情况下的吞吐量。如果线程本地的计算量较少,那么在锁和原子变量上的竞争将非常激烈。如果线程本地的计算量较多,那么在锁和原子变量上的竞争将会降低。因为在线程中访问锁和原子变量的频率将降低。

        从这些图中可以看出,在高度竞争的情况下,锁的性能将超过原子变量的性能,但在更真实的竞争情况下,原子变量的性能将超过锁的性能。这是因为锁在发生竞争时会挂起线程,从而降低了CPU的使用率和共享内存总线上的同步通信量(这类似于生产者--消费者设计中的可阻塞生产者,它能降低消费者上的工作负载,使消费者的处理速度赶上生产者的处理速度。)另一方面,如果使用原子变量,那么发出调用的类负责对竞争进行管理。与大多数基于CAS的算法一样,AtomicPseudoRandom在遇到竞争时将立即重试,这通常是一种正确的方法,但在激烈竞争环境下却导致了更多的竞争。下面两个图是粗略图,数值对应没那么精确,但足以表达相互之间的性能高低。


    在竞争程度较高情况下的Lock与AtomicInteger的性能


    在竞争程度适中情况下的Lock与AtomicInteger的性能

        在批评AtomicPseudoRandom写的太糟糕或者原子变量比锁更糟糕之前,应该意识到一图中竞争级别过高而有些不切实际:任何一个真实的程序都不会除了竞争锁或原子变量,其它什么工作都不做。在实际情况中,原子变量在可伸缩性上要高于锁,因为在应对常见的竞争程度时,原子变量的效率会更高。

        在中低端程度的竞争下,原子变量能提供更高的可伸缩性,而在高强度的竞争下,锁能够更有效地避免竞争。(在单CPU的系统上,基于CAS的算法在性能上同样会超过基于锁的算法,因为CAS在单CPU的系统上通常能执行成功,只有在偶然情况下,线程才会在执行读-改-写的操作过程中被其它线程抢占执行。)

        上两个图中都包含了第三条曲线,它是一个使用了ThreadLocal来保存PRNG状态的PseudoRandom。这种实现方法改变了类的行为,即每个线程都只能看到自己私有的伪随机数字序列,而不是所有线程共享同一个随机数序列,这说明了,如果能够避免使用共享状态,那么开销将会更小。我们可以通过提高处理竞争的效率来提高可伸缩性,但只有完全消除竞争,才能实现真正的可伸缩性。





    展开全文
  • Java原子类

    2021-01-06 21:11:51
    1.为什么需要原子类 当程序更新一个变量时,如果是多线程同时更新这个变量,可能得到的结果与期望值不同。比如:有一个变量i,A线程执行i+1,B线程也执行i+1,经过两个线程的操作后,变量i的值可能不是期望的3,而是...
  • 原子类操作

    2020-04-15 15:12:31
    Java中的原子操作大致可以分为4原子更新基本类型、原子更新数组类型、原子更新引用类型、原子更新属性类型。这些原子类中都是用了无锁的概念,有的地方直接使用CAS操作的线程安全的类型。 在JDK7,java原子类...
  • 原子性操作,意思是说CPU执行的过程中,操作的粒度如同原子,不可再细分中途不能被打断。众所周知如今CPU执行程序是时间片执行的,线程是CPU调度的单位。每一个线程的执行都会到一个时间片,当线程的时间片用完...
  • 原子操作分类原子更新基本类型原子更新数组原子更新引用类型原子更新字段累加器 分类 jdk1.5开始后,rt.jar增加了java.util.concurrent.atomic包,增加了一些专门进行原子操作的。如下表: 类型 java...
  • Java并发 原子操作

    2021-01-29 08:57:14
    Java Atomic包中有13个原子操作,共分为4,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。Atomic包里的基本都是使用Unsafe实现的包装原子更新基本类型 使用原子的方式...
  • 什么是原子类 一度认为原子是不可分割的最小单位,故原子类可以认为其操作都是不可分割 为什么要有原子类? 对多线程访问同一个变量,我们需要加锁,而锁是比较消耗性能的,JDk1.5之后, 新增的原子操作提供了一种...
  • Atomic 包下的原子操作有很多,可以大致分为四种类型: 原子操作基本类型 原子操作数组类型 原子操作引用类型 原子操作更新属性 Atomic原子操作在源码中都使用了Unsafe,Unsafe提供了硬件级别的原子操作,...
  • 文章目录背景java原子类java8中为什么要新增LongAdder?原理加法器(Adder)和累加器(Accumulator)LongAdderAccumulator应用场景demo举例demo1: 将1—1001数字相加参考 背景 java原子类 java8中为什么要新增LongAdder...
  • 什么是原子类原子类有什么作用? 要想回答这个问题,首先我们需要知道什么是原子类,以及它有什么作用。 在编程领域里,原子性意味着“一组操作要么全都操作成功,要么全都失败,不能只操作成功其中的一部分”。而...
  • JDK提供的原子类原理及使用volatile只能保障可见性,不能保障原子性,如value++操作,就不是一个原子性操作,value++共分为以下三步操作(假如value的值是0):1、取出value的值为0;2、将value的值进行加一操作,...
  • 我们为什么一定要学习 Atomic 包下的这些原子操作呢? 下面告诉你原因。 Java中有那么一些,是以Atomic开头的。这一系列的我们称之为原子操作。以最简单的AtomicInteger为例。它相当于一个int变量,我们...
  • 2、Unsafe功能介绍2.1、内存操作2.2、CAS相关2.3、线程调度2.4、内存屏障二:jdk提供的原子类1、基本2、数组类型原子类3、引用类型原子类4、属性原子修改器 前言 通过前面的文章可知。volatile关键字和sy
  • 维度是度量的基础,用来反映业务的一属性,这属性的集合构成一个维度,也可以称为一个实体对象。 在划分数据域、构建总线矩阵时,需要结合对业务过程的分析定义维度。 1.3 业务过程 业务过程是指企业的业务活动...
  • Java中的12个原子操作前言原子更新基本类型AtomicInteger代码示例AtomicBooleanAtomicLong原子更新数组AtomicIntegerArray代码示例AtomicLongArrayAtomicReferenceArray代码示例:原子更新引用类型AtomicReference...
  • 一、 原子操作介绍 在并发编程中很容易出现并发安全的问题,比如多个线程执行i++操作,就有可能获取不到正确的值,而这个问题,最常用的方法是通过Synchronized进行控制来达到线程安全的目的。但是由于...
  • 如果需要原子更新某个对象的某个字段,就需要使用原子更新属性的相关,atomic中提供了一下用于原子更新属性:  AtomicIntegerFieldUpdater:原子更新整形属性的更新器  AtomicLongFieldUpdater:原子...
  • JUC-Atomic(原子类

    2018-08-23 16:01:20
    2、原子类和java.lang.Integer等的区别 4、原子方式更新单个变量 1、API 2、变量的原子访问和更新 3、实现原理 5、原子方法更新数组 1、API 2、数组的原子访问和更新 3、实现原理 6、原子方式更新引用 1、...
  • 原子变量 (Atomics)是基于CAS实现的能够保障对共享变量进行read-modify-write更新操作的原子性和可见性的一组工具。这里所谓的read-modify-write更新操作,是指对共享变量的更新不是一个简单的赋值操作,而是...
  • 一般情况下如果我们想避免原子性问题的时都会选择加锁,但是我们都知道加锁和解锁是有消耗的。并且只要有加锁、解锁就会伴随着线程阻塞、线程的唤醒,这样线程的切换也是消耗性能的。...Java原子类可以分为五大...
  • 原子操作简介当更新一个变量的时候,多出现数据争用的时候可能出现所意想不到的情况。这时的一般策略是使用synchronized解决,因为synchronized能够保证多...
  • 文章目录1、简介2、原子更新基本类型2.1、AtomicInteger 实现原子操作的原理2.1.1、AtomicInteger 使用示例2.1.2、getAndIncrement 源码2.2、lazySet 方法是如何工作的2.2.1、简介2.2.2、好处2.2.3、如何实现2.3、...
  • 原子类的出现原因: 当程序更新一个变量时,如果是多线程同时更新这个变量,可能得到的结果与期望值不同。比如:有一个变量i,A线程执行i+1,B线程也执行i+1,经过两个线程的操作后,变量i的值可能不是期望的3,而是2...
  • Java并发编程-原子类总结 为了更好地支持原子性操作,并在同时规避使用synchronized关键字带来的系统开销(线程上下文切换),JUC中提供了一些原子操作原子操作主要依赖cas与volatile关键字实现线程安全。 ...
  • 前文我们介绍了Java并发编程中的两个关键字:volatile和synchronized。我们也知道了volatile虽然是轻量级,但不能保证原子性,synchronized可以保证原子性,但是比较重量级。...Atomic包下包含了12个,...
  • 01_原子键合种类

    千次阅读 2019-09-13 11:48:35
    原子键合种类 一次键:价电子转移或共用电子云达到稳定结构 离子键:价电子转移,键合很强,无方向性 共价键:价电子共用,键合强,有方向性 金属键:自由电子为阳离子所共有,键合较强,无方向性 二次键:靠...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 101,718
精华内容 40,687
关键字:

原子分为几类