Рубрики
Без рубрики

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

Исследуйте дескрипторы переменных в Java 9 с помощью API VarHandle.

Автор оригинала: baeldung.

1. введение

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

Одним из них является java.lang.invoke.VarHandle API – представление дескрипторов переменных, которые мы рассмотрим в этой статье.

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

Как правило, дескриптор переменной-это просто типизированная ссылка на переменную . Переменная может быть элементом массива, экземпляром или статическим полем класса.

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

Дескрипторы Var являются неизменяемыми и не имеют видимого состояния. Более того, они не могут быть разделены на подклассы.

Каждый Var-дескриптор имеет:

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

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

Цель VarHandle состоит в том, чтобы определить стандарт для вызова эквивалентов | java . util.concurrent.atomic и sun.misc.Unsafe | операций над полями и элементами массива.

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

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

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

Давайте объявим простой класс с различными переменными типа 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. Руководящие принципы и конвенции

В качестве конвенции мы должны заявить: Вархандл s как статический финал поля и явно инициализировать их в статических блоках. Кроме того, мы обычно используем заглавную версию соответствующего имени поля в качестве их имени.

Например, вот как сама Java использует VarHandle s внутренне для реализации 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);
    }
}

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

Теперь, когда мы это знаем, давайте двигаться дальше и посмотрим, как мы можем использовать их на практике.

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

Теперь мы можем получить VarHandle для нашей публичной тестовой переменной с помощью find Var Handle() метод :

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));

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

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

Если у нас есть закрытый член и нам нужен дескриптор переменной для такой переменной, мы можем получить это с помощью частного поиска В() методе :

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));

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

До Java 9 эквивалентным API для этой операции был класс Unsafe и метод setAccessible() из API Reflection .

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

Var Handle – лучшее и более быстрое решение в таких случаях.

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

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

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

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

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

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

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

Большинство методов VarHandle ожидают переменное число аргументов типа Object. Использование Объекта… в качестве аргумента отключает статическую проверку аргументов.

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

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

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

5. Переменные Режимы Доступа К Ручкам

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

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

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

Методы с уровнем доступа к чтению позволяют получить значение переменной при заданных эффектах упорядочения памяти. Существует несколько методов с этим режимом доступа, таких как: get() , get Acquire() , get Volatile() и get Opaque() .

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

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

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

5.2. Доступ на запись

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

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

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

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

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

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

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

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

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

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

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

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

Давайте посмотрим, как мы можем выполнять атомарные операции с помощью дескриптора Var :

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

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

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

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

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

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

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

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

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

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

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

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

Ранее мы упоминали, что методы VarHandle допускают доступ к переменным при определенных эффектах упорядочения памяти.

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

  • Простое чтение и запись гарантируют побитовую атомарность для ссылок и примитивов до 32 бит. Кроме того, они не накладывают никаких ограничений на порядок в отношении других признаков.
  • Непрозрачные операции являются побитовыми атомарными и когерентно упорядоченными по отношению к доступу к одной и той же переменной.
  • Получить и Освободить операции подчиняются Непрозрачным свойствам. Кроме того, Acquire reads будет заказан только после сопоставления Release mode записи.
  • Изменчивые операции полностью упорядочены по отношению друг к другу.

Очень важно помнить, что режимы доступа будут переопределять предыдущие эффекты упорядочения памяти . Это означает, что, например , если мы используем get () , это будет простая операция чтения, даже если мы объявили нашу переменную как volatile .

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

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

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

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

Как всегда, примеры кода доступны на GitHub .