Java 9 Variable Handles Demystified – Java 9 变量处理解密

最后修改: 2017年 12月 24日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

Java 9 brought a number of new useful features for developers.

Java 9为开发者带来了许多新的有用功能。

One of those is the java.lang.invoke.VarHandle API – representing variable handles – which we’re going to explore in this article.

其中之一是java.lang.invoke.VarHandle API – 代表变量句柄 – 我们将在这篇文章中探索它。

2. What Are Variable Handles?

2.什么是可变手柄?

Generally, a variable handle is just a typed reference to a variable. The variable can be an array element, instance, or static field of the class.

一般来说,一个变量句柄只是对一个变量的类型化引用。该变量可以是一个数组元素、实例或类的静态字段。

The VarHandle class provides write and read access to variables under specific conditions.

VarHandle类提供了在特定条件下对变量的写和读访问。

VarHandles are immutable and have no visible state. What’s more, they cannot be sub-classed.

VarHandles是不可变的,没有可见的状态。更重要的是,它们不能被子类化。

Each VarHandle has :

每个VarHandle都有:

  • a generic type T, which is the type of every variable represented by this VarHandle
  • a list of coordinate types CT, which are types of coordinate expressions, that allow locating variable referenced by this VarHandle

The list of coordinate types may be empty.

坐标类型的列表可能是空的。

The goal of VarHandle is to define a standard for invoking equivalents of java.util.concurrent.atomic and sun.misc.Unsafe operations on fields and array elements.

VarHandle的目标是为调用等同于java.util.concurrent.atomicsun.misc.Unsafe字段和数组元素的操作定义一个标准。

Those operations are mostly atomic or ordered operations — for example, atomic field incrementation.

这些操作大多是原子操作或有序操作–例如,原子字段递增。

3. Creating Variable Handles

3.创建变量句柄

To use VarHandle, we need to have variables first.

要使用VarHandle,我们首先需要有变量。

Let’s declare a simple class with different variables of type int that we’ll use in our examples:

让我们声明一个简单的类,其中有不同的int类型的变量,我们将在我们的例子中使用。

public class VariableHandlesUnitTest {
    public int publicTestVariable = 1;
    private int privateTestVariable = 1;
    public int variableToSet = 1;
    public int variableToCompareAndSet = 1;
    public int variableToGetAndAdd = 0;
    public byte variableToBitwiseOr = 0;
}

3.1. Guidelines and Conventions

3.1.准则和公约

As a convention, we should declare VarHandles as static final fields and explicitly initialize them in static blocks. Also, we usually use the uppercase version of the corresponding field name as their name. 

按照惯例,我们应该将VarHandles声明为static final字段,并在静态块中明确初始化它们。此外,我们通常使用相应字段名的大写版本作为其名称。

For instance, here’s how Java itself is using VarHandles internally to implement the AtomicReference:

例如,这里是Java本身如何在内部使用VarHandles来实现AtomicReference

private volatile V value;
private static final VarHandle VALUE;
static {
    try {
        MethodHandles.Lookup l = MethodHandles.lookup();
        VALUE = l.findVarHandle(AtomicReference.class, "value", Object.class);
    } catch (ReflectiveOperationException e) {
        throw new ExceptionInInitializerError(e);
    }
}

Most of the time, we can use the same pattern when using VarHandles.

大多数时候,我们在使用VarHandles时可以使用同样的模式。

Now that we know this, let’s move on and see how can we use them in practice.

现在我们知道了这些,让我们继续前进,看看我们如何在实践中使用它们。

3.2. Variable Handles for Public Variables

3.2.公有变量的变量处理

Now we can get a VarHandle for our publicTestVariable using the findVarHandle() method:

现在我们可以使用 findVarHandle()方法为我们的publicTestVariable获得一个VarHandle

VarHandle PUBLIC_TEST_VARIABLE = MethodHandles
  .lookup()
  .in(VariableHandlesUnitTest.class)
  .findVarHandle(VariableHandlesUnitTest.class, "publicTestVariable", int.class);

assertEquals(1, PUBLIC_TEST_VARIABLE.coordinateTypes().size());
assertEquals(VariableHandlesUnitTest.class, PUBLIC_TEST_VARIABLE.coordinateTypes().get(0));

We can see that the coordinateTypes property of this VarHandle isn’t empty and has one element, which is our VariableHandlesUnitTest class.

我们可以看到,这个VarHandlecoordinateTypes属性不是空的,有一个元素,就是我们的VariableHandlesUnitTest类。

3.3. Variable Handles for Private Variables

3.3.私有变量的变量处理

If we have a private member and we need a variable handle for such variable, we can obtain this using the privateLookupIn() method:

如果我们有一个私有成员,并且我们需要这样的变量的句柄,我们可以使用privateLookupIn()方法获得这个句柄

VarHandle PRIVATE_TEST_VARIABLE = MethodHandles
  .privateLookupIn(VariableHandlesUnitTest.class, MethodHandles.lookup())
  .findVarHandle(VariableHandlesUnitTest.class, "privateTestVariable", int.class);

assertEquals(1, PRIVATE_TEST_VARIABLE.coordinateTypes().size());
assertEquals(VariableHandlesUnitTest.class, PRIVATE_TEST_VARIABLE.coordinateTypes().get(0));

Here, we chose the privateLookupIn() method which has broader access than the normal lookup(). This allows us to get access to private, public, or protected variables.

在这里,我们选择了privateLookupIn()方法,它比普通的lookup()具有更广泛的访问权限。这使得我们可以访问privatepublicprotected变量。

Before Java 9, the equivalent API for this operation was the Unsafe class and the setAccessible() method from the Reflection API.

在Java 9之前,该操作的对应API是Unsafe类和Reflection API中的setAccessible()方法。

However, this approach has its disadvantages. For example, it will only work for the specific instance of the variable.

然而,这种方法也有其缺点。例如,它只对变量的特定实例起作用。

VarHandle is a better and faster solution in such cases.

在这种情况下,VarHandle是一个更好、更快的解决方案。

3.4. Variable Handles for Arrays

3.4.数组的变量处理

We could use the previous syntax to obtain array fields.

我们可以使用之前的语法来获得数组字段。

However, we can also get the VarHandle for an array of a specific type:

然而,我们也可以为一个特定类型的数组获取VarHandle

VarHandle arrayVarHandle = MethodHandles.arrayElementVarHandle(int[].class);

assertEquals(2, arrayVarHandle.coordinateTypes().size());
assertEquals(int[].class, arrayVarHandle.coordinateTypes().get(0));

We can now see that such VarHandle has two coordinate types int and [], which represent an array of int primitives.

我们现在可以看到,这样的VarHandle有两个坐标类型int[],它们表示一个int基元的数组。

4. Invoking VarHandle Methods

4.调用VarHandle方法

Most of the VarHandle methods expect a variable number of arguments of type Object. Using Object… as an argument disables static argument checking.

大多数VarHandle方法都期望有一个可变数量的Object类型的参数。使用Object…作为一个参数,可以禁用静态参数检查。

All the argument checking is done at runtime. Also, different methods expect to have a different number of arguments of different types.

所有的参数检查都是在运行时完成的。另外,不同的方法期望有不同类型的参数数量。

If we fail to give a proper number of arguments with proper types, the method call will throw a WrongMethodTypeException.

如果我们没有给出适当数量和适当类型的参数,方法调用将抛出一个WrongMethodTypeException

For example, get() will expect at least one argument, which helps to locate variable, but set() expects one more argument, which is the value to be assigned to the variable.

例如,get()将期望至少有一个参数,这有助于定位变量,但set()期望多一个参数,这是要分配给变量的值。

5. Variable Handles Access Modes

5.可变手柄的访问模式

Generally, all the methods of the VarHandle class fall to five different access modes.

一般来说,VarHandle类的所有方法都属于五个不同的访问模式。

Let’s go through each of them in the next sub-sections.

让我们在接下来的小节中逐一介绍。

5.1. Read Access

5.1.阅读权限

Methods with reading access level allow getting the value of the variable under specified memory ordering effects. There are several methods with this access mode like: get(), getAcquire(), getVolatile() and getOpaque().

具有读取访问级别的方法允许在指定的内存排序效果下获得变量的值。有几个具有这种访问模式的方法,比如。get(), getAcquire(), getVolatile()getOpaque()

We can easily use the get() method on our VarHandle:

我们可以轻松地在我们的VarHandle上使用get()方法。

assertEquals(1, (int) PUBLIC_TEST_VARIABLE.get(this));

The get() method takes only CoordinateTypes as parameters, so we can simply use this in our case.

get()方法只接受CoordinateTypes作为参数,所以我们可以在我们的案例中简单地使用this

5.2. Write Access

5.2.写访问

Methods with writing access level allow us to set the value of the variable under specific memory ordering effects.

具有写入访问级别的方法允许我们在特定的内存排序效果下设置变量的值。

Similarly to methods with read access, we have several methods with write access: set(), setOpaque(), setVolatile(), and setRelease().

与具有读取权限的方法类似,我们有几个具有写入权限的方法。set(), setOpaque(), setVolatile(), 和setRelease()

We can use the set() method on our VarHandle:

我们可以在我们的VarHandle上使用set()方法。

VARIABLE_TO_SET.set(this, 15);
assertEquals(15, (int) VARIABLE_TO_SET.get(this));

The set() method expects at least two arguments. The first one will help locate the variable, while the second is the value to be set to the variable.

set()方法期望至少有两个参数。第一个参数将帮助定位变量,而第二个参数是要设置到该变量的值。

5.3. Atomic Update Access

5.3.原子更新访问

Methods with this access level can be used to atomically update the value of the variable.

具有这种访问级别的方法可以被用来原子化地更新变量的值。

Let’s use the compareAndSet() method to see the effects:

让我们使用compareAndSet()方法来看看效果。

VARIABLE_TO_COMPARE_AND_SET.compareAndSet(this, 1, 100);
assertEquals(100, (int) VARIABLE_TO_COMPARE_AND_SET.get(this));

Apart from the CoordinateTypes, the compareAndSet() method takes two additional values: oldValue and newValue. The method sets the value of the variable if it was equal to oldVariable or leaves it unchanged otherwise.

除了CoordinateTypes之外,compareAndSet()方法还需要两个额外的值。oldValuenewValue。如果变量的值等于oldVariable,该方法就设置它的值,否则就保持不变。

5.4. Numeric Atomic Update Access

5.4.数值原子更新访问

These methods allow performing numeric operations such as getAndAdd() under specific memory ordering effects.

这些方法允许在特定的内存排序效果下执行数字操作,如getAndAdd()。

Let’s see how we can perform atomic operations using a VarHandle:

让我们看看我们如何使用VarHandle来执行原子操作。

int before = (int) VARIABLE_TO_GET_AND_ADD.getAndAdd(this, 200);

assertEquals(0, before);
assertEquals(200, (int) VARIABLE_TO_GET_AND_ADD.get(this));

Here, the getAndAdd() method first returns the value of the variable, then adds the provided value.

这里,getAndAdd()方法首先返回变量的值,然后添加所提供的值。

5.5. Bitwise Atomic Update Access

5.5.位数原子更新访问

Methods with this access allow us to atomically perform bitwise operations under specific memory ordering effects.

具有这种访问权限的方法允许我们在特定的内存排序效果下原子化地进行位操作。

Let’s see an example of using the getAndBitwiseOr() method:

让我们看一个使用getAndBitwiseOr()方法的例子。

byte before = (byte) VARIABLE_TO_BITWISE_OR.getAndBitwiseOr(this, (byte) 127);

assertEquals(0, before);
assertEquals(127, (byte) VARIABLE_TO_BITWISE_OR.get(this));

This method will get the value of our variable and perform a bitwise OR operation on it.

这个方法将获得我们的变量的值,并对其进行位数OR操作。

The method call will throw an IllegalAccessException if it fails to match the access mode required by the method with the one allowed by the variable.

如果方法要求的访问模式与变量允许的访问模式不匹配,方法调用将抛出一个 IllegalAccessException

For example, this will happen if we try to use a set() method on a final variable.

例如,如果我们试图在一个final变量上使用set()方法,就会发生这种情况。

6. Memory Ordering Effects

6.记忆排序效应

We previously mentioned that VarHandle methods allow access to variables under specific memory ordering effects.

我们之前提到,VarHandle方法允许在特定内存排序效果下访问变量。

For most of the methods there are 4 memory ordering effects:

对于大多数方法来说,有4种内存排序效果。

  • Plain reads and writes guarantee bitwise atomicity for references and primitives under 32 bits. Also, they impose no ordering constraints with respect to the other traits.
  • Opaque operations are bitwise atomic and coherently ordered with respect to access to the same variable.
  • Acquire and Release operations obey Opaque properties. Also, Acquire reads will be ordered only after matching Release mode writes.
  • Volatile operations are fully ordered with respect to each other.

It’s very important to remember that access modes will override previous memory ordering effects. This means that, for example, if we use get(), it will be a plain read operation, even if we declared our variable as volatile.

记住访问模式将覆盖之前的内存排序效果是非常重要的。这意味着,例如,如果我们使用get(),它将是一个普通的读操作,即使我们将我们的变量声明为volatile

Because of that, developers must use extreme caution when they use VarHandle operations.

正因为如此,开发者在使用VarHandle操作时必须非常谨慎。

7. Conclusion

7.结论

In this tutorial, we presented variable handles and how to use them.

在本教程中,我们介绍了变量手柄以及如何使用它们。

This topic is quite complicated since variable handles aim to allow low-level manipulation and they should not be used unless necessary.

这个话题相当复杂,因为变量句柄的目的是允许低级别的操作,除非必要,否则不应该使用它们。

As always, the code samples are available over on GitHub.

一如既往,代码样本可在GitHub上获得