Java 深入分析 - 并发 原子量

Volitale

并发中,锁主要提供两种特性:互斥可见

并发情况下,一个线程先到来,给对象上锁,其他线程只能在后面排队,这种互斥带给我们的是操作的 原子性,即在并发的情况下,一个操作不会受其他线程的影响

另外,每个线程在读取变量时,自己都会缓存一份,也就是说,可能下次读取操作便不会经过内存,而是直接取缓存,当然,这在并发条件下,线程共享的变量会有问题是理所当然的,如果每个线程在访问一个变量时,每次都从内存中重新读取值,那么我们认为这个变量对于每个线程来说具有 可见性

当然,若你不需要互斥,只需要保证可见性,Java 提供了一种更轻量的语法 volitale,经过 volitale 修饰的变量,可以保证:

  • 可见性
  • 防止指令重排

在执行代码时,代码顺序为 A -> B -> C,CPU 为了执行效率,可能会将其顺序打乱,当然,为什么我们在单线程条件下执行却是有序的,原因是指令重排遵循 as-if-serial 语义,即无论如何重排,都不会影响结果,就像如果 C 依赖于 AB,那么总是会在 A
B 都执行完后,才会执行 A

显而易见的是 volitale 并不支持原子性,就像 i++ 的执行过程:

  • 从内存中取出 i
  • i 加 1
  • 再将结果赋值给 i

volitale 有一种写法叫 读 - 写锁,它本身是矛盾的,在我们使用这种写法时,我们认为读取应该可以是不严谨的,如果超越了该模式的最基本应用,结合这两个竞争的同步机制将变得非常困难

    private volatile int value;

    public int getValue() { return value; }

    public synchronized int increment() {
        return value++;
    }

Unsafe

正如它的名字一样,Unsafe 是一个非常不安全的类

并发包下 Unsafe 提供以下功能:

  • 直接操作内存地址
  • 恢复与挂起线程
  • 开放汇编、CPU级别的 CAS [1] 操作

我们可以认为,Jdk 并发包是在 Unsafe 下建立的,它的构造函数是被类加载器封死的,外包下可以通过反射获取,但使用 Unsafe 类,请务必对它十分清楚

Atomic 系列类

如果我们想为自己的对象原子化,AtomicReference 是一种很好的选择,AtomicReference 内部已经维护好了一个 Unsafe内存偏移地址,也开放了一些原子操作的入口

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

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
    private volatile V value;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicReference.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    // ...
}

如果我们想为自己的对象数组原子化,AtomicReferenceArray 是一种很好的选择

如果我们想为自己的数字变量原子化,可以选择一系列的 AtomicIntegerAtomicDouble ...

它们的实现大致相同,内部都是基于 Unsafe 构建的,我们在这里就不一一详细讨论了


  1. 无锁自旋同步 CAS 操作详解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容