Демифицированные дескрипторы переменных Java 9

Переменные дескрипторы Java 9

1. Вступление

Java 9 принесла много новых полезных функций для разработчиков.

Одним из них является APIjava.lang.invoke.VarHandle, представляющий дескрипторы переменных, который мы собираемся изучить в этой статье.

2. Что такое переменные дескрипторы?

Обычноa variable handle is just a typed reference to a variable. Переменная может быть элементом массива, экземпляром или статическим полем класса.

КлассVarHandle обеспечивает доступ для записи и чтения к переменным при определенных условиях.

VarHandles неизменяемы и не имеют видимого состояния. Более того, их нельзя разделить на подклассы.

КаждыйVarHandle имеет:

  • общий типT, который является типом каждой переменной, представленной этимVarHandle

  • список типов координатCT, которые являются типами координатных выражений, которые позволяют найти переменную, на которую ссылается этотVarHandle

Список типов координат может быть пустым.

ЦельVarHandle - определить стандарт для вызова операцийequivalents ofjava.util.concurrent.atomic иsun.misc.Unsafe над полями и элементами массива.

Эти операции в большинстве случаев являются атомарными или упорядоченными операциями. Например, приращение атомного поля.

3. Создание дескрипторов переменных

Чтобы использоватьVarHandle, нам сначала нужны переменные.

Давайте объявим простой класс с разными переменными типаint, которые мы будем использовать в наших примерах:

public class VariableHandlesTest {
    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. Дескрипторы переменных для общедоступных переменных

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

VarHandle publicIntHandle = MethodHandles.lookup()
  .in(VariableHandlesTest.class)
  .findVarHandle(VariableHandlesTest.class, "publicTestVariable", int.class);

assertThat(
  publicIntHandle.coordinateTypes().size() == 1);
assertThat(
  publicIntHandle.coordinateTypes().get(0) == VariableHandles.class);

Мы видим, чтоcoordinateTypes из этогоVarHandle не пуст и имеет один элемент, который является нашим классомVariableHandlesTest.

3.2. Дескрипторы переменных для частных переменных

Если у нас есть частный член и нам нужен дескриптор переменной для такой переменнойwe can obtain this using the privateLookupIn() method:

VarHandle privateIntHandle = MethodHandles
  .privateLookupIn(VariableHandlesTest.class, MethodHandles.lookup())
  .findVarHandle(VariableHandlesTest.class, "privateTestVariable", int.class);

assertThat(privateIntHandle.coordinateTypes().size() == 1);
assertThat(privateIntHandle.coordinateTypes().get(0) == VariableHandlesTest.class);

Здесь мы выбрали методprivateLookupIn(), который имеет более широкий доступ, чем обычныйlookup().. Это позволяет нам получить доступ к переменнымprivate, public илиprotected.

До Java 9 эквивалентным API для этой операции был методUnsafe class_ and the _setAccessible() из APIReflection.

Однако у этого подхода есть свои недостатки. Например, он будет работать только для конкретного экземпляра переменной.

VarHandle - лучшее и быстрое решение в таких случаях.

3.3. Переменные дескрипторы для массивов

Мы могли бы использовать предыдущий синтаксис для получения полей массива.

Однако мы также можем получитьVarHandle для массива определенного типа:

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

assertThat(arrayVarHandle.coordinateTypes().size() == 2);
assertThat(arrayVarHandle.coordinateTypes().get(0) == int[].class);

Теперь мы можем видеть, что такойVarHandle имеет два типа координатint и[], которые представляют собой массив примитивовint.

4. Вызов методовVarHandle

Most of the VarHandle methods expect a variable number of arguments of typeObject. ИспользованиеObject… в качестве аргумента отключает статическую проверку аргументов.

Вся проверка аргументов выполняется во время выполнения. Кроме того, разные методы ожидают разного количества аргументов разных типов.

Если мы не сможем предоставить правильное количество аргументов с правильными типами, вызов метода выдастWrongMethodTypeException.

Например,get() ожидает по крайней мере один аргумент, который помогает найти переменную, ноset() ожидает еще один аргумент, который является значением, которое будет присвоено переменной.

5. Переменные дескрипторы режимов доступа

Как правило, все методы классаVarHandle __ относятся к пяти различным режимам доступа.

Давайте рассмотрим каждый из них в следующих подразделах.

5.1. Доступ для чтения

Методы с уровнем доступа для чтения позволяют получить значение переменной при заданных эффектах упорядочения памяти. Есть несколько методов с этим режимом доступа, например:get(), getAcquire(), getVolatile() иgetOpaque().

Мы можем легко использовать методget() для нашегоVarHandle:

assertThat((int) publicIntHandle.get(this) == 1);

Методget() принимает в качестве параметров толькоCoordinateTypes, поэтому в нашем случае мы можем просто использоватьthis.

5.2. Доступ для записи

Методы с уровнем доступа записи позволяют нам устанавливать значение переменной под конкретные эффекты упорядочения памяти.

Подобно методам с доступом на чтение, у нас есть несколько методов с доступом на запись:set(), setOpaque(), setVolatile() иsetRelease().

Мы можем использовать методset() для нашегоVarHandle:

publicIntHandle.set(this, 15);

assertThat((int) publicIntHandle.get(this) == 15);

Методset() ожидает как минимум два аргумента. Первый поможет найти переменную, а второй - значение, которое будет установлено для переменной.

5.3. Доступ к атомарному обновлению

Методы с этим уровнем доступа могут использоваться для атомарного обновления значения переменной.

Давайте воспользуемся методомcompareAndSet(), чтобы увидеть эффекты:

publicIntHandle.compareAndSet(this, 1, 100);

assertThat((int) publicIntHandle.get(this) == 100);

ПомимоCoordinateTypes, thecompareAndSet(), метод принимает два дополнительных значения:oldValue иnewValue.. Метод устанавливает значение переменной, если оно было равноoldVariable или оставляет в остальном она не изменилась.

5.4. Доступ к числовому атомарному обновлению

Эти методы позволяют выполнять числовые операции, такие какgetAndAdd (), при определенных эффектах упорядочения памяти.

Давайте посмотрим, как мы можем выполнять атомарные операции, используяVarHandle:

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

assertThat(before == 0);
assertThat((int) publicIntHandle.get(this) == 200);

Здесь методgetAndAdd() сначала возвращает значение переменной, а затем добавляет предоставленное значение.

5.5. Побитовый атомарный доступ к обновлению

Методы с таким доступом позволяют атомарно выполнять побитовые операции под определенными эффектами упорядочения памяти.

Давайте посмотрим на пример использования методаgetAndBitwiseOr():

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

assertThat(before == 0);
assertThat(variableToBitwiseOr == 127);

Этот метод получит значение нашей переменной и выполнит над ней побитовую операцию ИЛИ.

Вызов метода выдастIllegalAccessException, если он не сможет сопоставить режим доступа, требуемый для метода, с режимом, разрешенным переменной.

Например, это произойдет, если мы попытаемся использовать методset() для переменнойfinal.

6. Эффекты упорядочивания памяти

Ранее мы упоминали, чтоVarHandle methods allow access to variables under specific memory ordering effects.

Для большинства методов есть 4 эффекта упорядочения памяти:

  • Операции чтения и записиPlain гарантируют поразрядную атомарность для ссылок и примитивов размером менее 32 бит. Кроме того, они не накладывают никаких ограничений на порядок относительно других признаков.

  • ОперацииOpaque являются побитовыми атомарными и последовательно упорядочены относительно доступа к одной и той же переменной.

  • ОперацииAcquire иRelease подчиняются свойствамOpaque. Кроме того, чтенияAcquire будут упорядочены только после сопоставления записи в режимеRelease.

  • Volatile операции полностью упорядочены друг относительно друга.

Очень важно помнить, чтоaccess modes will override previous memory ordering effects. Это означает, что, например, если мы используемget(), это будет обычная операция чтения, даже если мы объявили нашу переменную какvolatile.

По этой причине разработчики должны проявлять крайнюю осторожность при использовании операцийVarHandle.

7. Заключение

В этом уроке мы представили переменные дескрипторы и как их использовать.

Эта тема довольно сложная, поскольку переменные маркеры предназначены для низкоуровневых манипуляций, и их не следует использовать без необходимости.

Как всегда доступны образцы кодаover on GitHub.