用Variable Handles来替换Unsafe

发布时间 2023-11-07 14:51:23作者: mingshan

在JDK9中,包含了一个叫Variable Handles的新功能,下面是该功能的描述:

Define a standard means to invoke the equivalents of various java.util.concurrent.atomic and sun.misc.Unsafe operations upon object fields and array elements, a standard set of fence operations for fine-grained control of memory ordering, and a standard reachability-fence operation to ensure that a referenced object remains strongly reachable.

从这段官方描述中我们可以得知,Variable Handles主要是提供java.util.concurrent.atomicsun.misc.Unsafe相似的功能,但会更加安全和易用,并且在并发方面提高了性能。在Java并发包中,由于包中的类大量调用sun.misc.Unsafe提供的功能,官方现在已不推荐再使用sun.misc.Unsafe这个类了,所以看看这个功能十分有必要,例如在AtomicIntegerArray这个类中已经用Variable Handles替换了sun.misc.Unsafe,并且在CAS中也会用到。

说到CAS的应用,我们需要对某一字段进行原子更新,可能会用到以下几种方式:

  1. 使用AtomicInteger类来更新,但会带来额外的内存消耗以及因引用替换带来的新的并发问题;
  2. 使用Atomic*FieldUpdater类来完成字段的原子更新,其是基于反射来实现的,对字段和包装类型有一定的限制,操作开销比较大;
  3. 使用sun.misc.Unsafe提供的JVM内置函数API,这个类提供不安全的操作,损害安全性和移植性,官方不允许开发者使用,以后也会被替代掉。

变量句柄是对变量的类型化引用,它支持在各种访问模式下对变量的读写访问。支持的变量类型包括实例字段、静态字段和数组元素。下面我们来看看它的基础API如何使用。

创建VarHandle需要通过MethodHandles这个类调用其静态方法来实现,根据要访问类的不同类型的成员变量调用不同的静态方法:

  • MethodHandles.lookup 访问类非私有属性
  • MethodHandles.privateLookupIn 访问类的私有属性
  • MethodHandles.arrayElementVarHandle 访问类中的数组

获取到Lookup,然后通过调用findVarHandle方法来获取VarHandle实例,在JDK9中,

  • findVarHandle:用于创建对象中非静态字段的VarHandle。接收参数有三个,第一个为接收者的Class对象,第二个是字段名称,第三个是字段类型。
  • findStaticVarHandle:用于创建对象中静态字段的VarHandle,接收参数与findVarHandle一致。
  • unreflectVarHandle:通过反射字段创建VarHandle。

为了保证效率,VarHandle类的实例通常需要被声明为static final变量(其实就是常量),这样可以在编译期对它进行优化。代码如下:

private static final VarHandle VH_TEST_FIELD_I;
private static final VarHandle VH_TEST_ARRAY;
private static final VarHandle VH_TEST_FIELD_J;

int i = 1;
int[] arr = new int[]{1, 2, 3};
private int j = 2;

static {
    try {
        VH_TEST_FIELD_I = MethodHandles.lookup()
                .in(Test.class)
                .findVarHandle(Test.class, "i", int.class);

        VH_TEST_ARRAY = MethodHandles.arrayElementVarHandle(int[].class);

        VH_TEST_FIELD_J = MethodHandles.privateLookupIn(Test.class, MethodHandles.lookup())
                .findVarHandle(Test.class, "j", int.class);
    } catch (ReflectiveOperationException e) {
        throw new Error(e);
    }
}

获取到了VarHandle实例,接下来可以做些什么呢?VarHandle提供了几种访问模式(access modes):

  1. read access modes, such as reading a variable with volatile memory ordering effects;
  2. write access modes, such as updating a variable with release memory ordering effects;
  3. atomic update access modes, such as a compare-and-set on a variable with volatile memory order effects for both read and writing;
  4. numeric atomic update access modes, such as get-and-add with plain memory order effects for writing and acquire memory order effects for reading.
  5. bitwise atomic update access modes, such as get-and-bitwise-and with release memory order effects for writing and plain memory order effects for reading.

后面三个访问模式被称为 read-modify-write 。从上面5个访问模式来看,主要包括普通变量读写、volatile变量读写和CAS操作。示例代码如下:

public void test() {
    // read
    System.out.println(VH_this_FIELD_I.get(this)); // 1
    System.out.println(VH_this_ARRAY.get(arr, 2)); // 3

    // write
    VH_this_FIELD_I.set(this, 99);
    System.out.println(VH_this_FIELD_I.get(this)); // 99

    VH_this_ARRAY.set(arr, 2, 4);
    System.out.println(VH_this_ARRAY.get(arr, 2)); // 4

    // CAS
    System.out.println(VH_this_FIELD_I.compareAndSet(this, 99, 3)); // true
    System.out.println(VH_this_FIELD_I.get(this)); // 3

    System.out.println(VH_this_ARRAY.compareAndSet(arr, 2, 4, 8)); // true
    System.out.println(VH_this_ARRAY.get(arr, 2)); // 8

    // Numeric Atomic Update
    System.out.println(VH_this_FIELD_I.getAndAdd(this, 6)); // 3
    System.out.println(VH_this_FIELD_I.get(this)); // 9
}

除了上面的五中访问模式外,在JEP 193的描述中,还提供了一组内存屏障(Memory fences)方法,为内存排序提供更细粒度的控制。其中涉及到memory order的概念,与最近的c++ 11内存模型一致,详情请参考其相关概念。

在想要利用CAS去实现我们的逻辑,首先推荐使用VarHandle来实现,它提供了精细粒度的API,更加安全,相信在以后的产品中应用会非常广泛。

参考:


title: 用Variable Handles来替换Unsafe
tags: [VarHandle,java]
author: Mingshan
categories: Java
date: 2018-10-05