atomic包

java.util.concurrent.atomic 包

类的小工具包,支持在单个变量上解除锁的线程安全编程。事实上,此包中的类可将 volatile 值、字段和数组元素的概念扩展到那些也提供原子条件更新操作的类,其形式如下:

boolean compareAndSet(expectedValue, updateValue);

如果此方法(在不同的类间参数类型也不同)当前保持 expectedValue,则以原子方式将变量设置为 updateValue,并在成功时报告true。此包中的类还包含获取并无条件设置值的方法,以及以下描述的较弱条件的原子更新操作 weakCompareAndSet。

这些方法的规范使实现能够使用当代处理器上提供的高效机器级别原子指令。但是在某些平台上,该支持可能需要某种形式的内部锁。因而,该方法不能严格保证不被阻塞 - 执行操作之前可能暂时阻塞线程。

类 AtomicBoolean、AtomicInteger、AtomicLong 和 AtomicReference 的实例各自提供对相应类型单个变量的访问和更新。每个类也为该类型提供适当的实用工具方法。例如,类 AtomicLong 和 AtomicInteger 提供了原子增量方法。一个应用程序将按以下方式生成序列号:

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

原子访问和更新的内存效果一般遵循以下可变规则,正如 The Java Language Specification, Third Edition (17.4 Memory Model) 中的声明:

  • get 具有读取 volatile 变量的内存效果。
  • set 具有写入(分配)volatile 变量的内存效果。
  • 除了允许使用后续(但不是以前的)内存操作,其自身不施加带有普通的非 volatile 写入的重新排序约束,lazySet 具有写入(分配)volatile 变量的内存效果。在其他使用上下文中,当为 null 时(为了垃圾回收),lazySet 可以应用不会再次访问的引用。
  • weakCompareAndSet 以原子方式读取和有条件地写入变量但不创建任何 happen-before 排序,因此不提供与除 weakCompareAndSet 目标外任何变量以前或后续读取或写入操作有关的任何保证。
  • compareAndSet 和所有其他的读取和更新操作(如 getAndIncrement)都有读取和写入 volatile 变量的内存效果。

除了包含表示单个值的类之外,此包还包含 Updater 类,该类可用于获取任意选定类的任意选定 volatile 字段上
的 compareAndSet 操作。AtomicReferenceFieldUpdater、AtomicIntegerFieldUpdater 和 AtomicLongFieldUpdater 是基于反射的实用工具,可以提供对关联字段类型的访问。它们主要用于原子数据结构中,该结构中同一节点(例如,树节点的链接)的几个 volatile 字段都独立受原子更新控制。这些类在如何以及何时使用原子更新方面具有更大的灵活性,但相应的弊端是基于映射的设置较为拙笨、使用不太方便,而且在保证方面也较差。

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

原子类也支持 weakCompareAndSet 方法,该方法具有受限制的适用性。在某些平台上,弱版本在正常情况下可能比 compareAndSet 更有效,但不同的是 weakCompareAndSet 方法的任何给定调用可能意外 返回 false(即没有明确的原因)。返回 false 仅意味着可以在需要时重新尝试操作,具体取决于重复执行调用的保证,当该变量保持 expectedValue 并且没有其他线程也在尝试设置该变量时,最终将获得成功。(例如,这样的虚假失败可能是由于内存争用的结果,该争用与期望值和当前值是否相等无关)。 此外,weakCompareAndSet 不提供通常需要同步控制的排序保证。但是,在这样的更新与程序的其他 happen-before 排序不相关时,该方法可用于更新计数器和统计数据。当一个线程看到对 weakCompareAndSet 导致的原子变量的更新时,它不一定能看到在 weakCompareAndSet 之前发生的对任何其他 变量的更新。例如,在更新性能统计数据时,这也许可以接受,但其他情况几乎不可以。

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

设计原子类主要用作各种构造块,用于实现非阻塞数据结构和相关的基础结构类。compareAndSet 方法不是锁的常规替换方法。仅当对象的重要更新限定于单个 变量时才应用它。

原子类不是 java.lang.Integer 和相关类的通用替换方法。它们不定义诸如 hashCode 和 compareTo 之类的方法。(因为原子变量是可变的,所以对于哈希表键来说,它们不是好的选择。)另外,仅为那些通常在预期应用程序中使用的类型提供类。例如,没有表示 byte 的原子类。这种情况不常见,如果要这样做,可以使用 AtomicInteger 来保持 byte 值,并进行适当的强制转换。也可以使用 Float.floatToIntBits 和 Float.intBitstoFloat 转换来保持 float 值,使用 Double.doubleToLongBits 和 Double.longBitsToDouble 转换来保持 double 值。

AtomicReference 和 volatile 的区别

AtomicReference 提供了如下的方法:

  • compareAndSet(V expect, V update):如果当前保存的值等于 expect, 会自动设置新的值 update.
  • getAndSet(V newValue):自动设置给定的值 newValue, 并返回旧的数值.
  • lazySet(V newValue):最终设置为给定值 newValue.
  • set(V newValue): 设置为给定值.
  • get():获取当前值.

AtomicReference 中的CAS操作就是compareAndSet(),其作用是每次从内存中根据内存偏移量(valueOffset)取出数据,将取出的值跟 expect 比较,如果数据一致就把内存中的值改为 update。

Simple Usage
如RxJava中, SingleScheduler 里executor的设置

public final class SingleScheduler extends Scheduler {
    final AtomicReference<ScheduledExecutorService> executor = new AtomicReference<ScheduledExecutorService>();

    public SingleScheduler(ThreadFactory threadFactory) {
        this.threadFactory = threadFactory;
        executor.lazySet(createExecutor(threadFactory));
    }

    @Override
    public void start() {
        ScheduledExecutorService next = null;
        for (;;) {
            ScheduledExecutorService current = executor.get();
            if (current != SHUTDOWN) {
                if (next != null) {
                    next.shutdown();
                }
                return;
            }
            if (next == null) {
                next = createExecutor(threadFactory);
            }
            if (executor.compareAndSet(current, next)) {
                return;
            }

        }
    }
}

这里还有两个概念需要理解, 独占锁和乐观锁

  1. 独占锁就是线程获取锁后其他的线程都需要挂起,直到持有独占锁的线程释放锁, 如synchronized
  2. 乐观锁是先假定没有冲突直接进行操作,如果因为有冲突而失败就重试,直到操作成功。其中乐观锁用到的机制就是CAS,Compare and Swap, 如atomic包下的 AtomicReference 等等。

AtomicReference源码,

public class AtomicReference<V> implements java.io.Serializable {

    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
    private static final long VALUE;

    static {
        try {
            VALUE = U.objectFieldOffset
                (AtomicReference.class.getDeclaredField("value"));
        } catch (ReflectiveOperationException e) {
            throw new Error(e);
        }
    }

    private volatile V value;

    ...
}

关键的成员属性 value 是使用 volatile 修饰来达到并发的效果, 使用独占锁 synchronized 的话, 会阻塞其他线程,导致并发效率大打折扣

而volatile包含以下语义:

  • Java 存储模型不会对valatile指令的操作进行重排序:这个保证对volatile变量的操作时按照指令的出现顺序执行的。
    volatile变量不会被缓存在寄存器中(只有拥有线程可见)或者其他对CPU不可见的地方,每次总是从主存中读取

  • volatile变量的结果。也就是说对于volatile变量的修改,其它线程总是可见的,并且不是使用自己线程栈内部的变量。也就是在happens-before法则中,对一个valatile变量的写操作后,其后的任何读操作都是可见此写操作
    的结果。

区别:

  1. volatile 修饰的变量, 并不是线程安全的, 而 AtomicReference 是线程安全的, 具有原子性
  2. 对 volatile 变量的读取和写入操作导致变量直接在主存中读写, 比在 cpu 缓存中代价更高; volatile 修饰时阻止了JVM的性能优化技术对指令的重排序。