Identificadores variáveis ​​do Java 9 desmistificados

Identificadores variáveis ​​do Java 9 desmistificados

1. Introdução

O Java 9 trouxe vários novos recursos úteis para desenvolvedores.

Uma delas é a APIjava.lang.invoke.VarHandle - representando identificadores de variáveis ​​- que vamos explorar neste artigo.

2. O que são identificadores de variáveis?

Geralmente,a variable handle is just a typed reference to a variable. A variável pode ser um elemento da matriz, instância ou campo estático da classe.

A classeVarHandle fornece acesso de gravação e leitura a variáveis ​​sob condições específicas.

VarHandles são imutáveis ​​e não têm estado visível. Além do mais, eles não podem ser subclassificados.

CadaVarHandle tem:

  • um tipo genéricoT, que é o tipo de cada variável representada por esteVarHandle

  • uma lista de tipos de coordenadasCT, que são tipos de expressões de coordenadas, que permitem localizar a variável referenciada por esteVarHandle

A lista de tipos de coordenadas pode estar vazia.

O objetivo deVarHandle é definir um padrão para invocarequivalents ofjava.util.concurrent.atomicesun.misc.Unsafe operações em campos e elementos de array.

Essas operações são na maioria operações atômicas ou ordenadas. Por exemplo, incremento de campo atômico.

3. Criação de identificadores variáveis

Para usar oVarHandle, precisamos primeiro ter variáveis.

Vamos declarar uma classe simples com diferentes variáveis ​​do tipoint que usaremos em nossos exemplos:

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. Identificadores de variáveis ​​para variáveis ​​públicas

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

Podemos ver quecoordinateTypes desteVarHandle não está vazio e tem um elemento, que é nossa classeVariableHandlesTest.

3.2. Manipuladores de variáveis ​​para variáveis ​​privadas

Se tivermos um membro privado e precisarmos de um identificador de variável para essa variávelwe 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);

Aqui, escolhemos o métodoprivateLookupIn() que tem acesso mais amplo do que olookup(). normal. Isso nos permite obter acesso às variáveisprivate, public ouprotected.

Antes do Java 9, a API equivalente para esta operação era o métodoUnsafe class_ and the _setAccessible() da APIReflection.

No entanto, essa abordagem tem suas desvantagens. Por exemplo, ele funcionará apenas para a instância específica da variável.

VarHandle é uma solução melhor e mais rápida em tais casos.

3.3. Identificadores variáveis ​​para matrizes

Poderíamos usar a sintaxe anterior para obter campos de matriz.

No entanto, também podemos obterVarHandle para uma matriz de tipo específico:

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

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

Agora podemos ver que talVarHandle tem dois tipos de coordenadasint e[], que representam uma matriz de primitivasint.

4. Invocando métodosVarHandle

Most of the VarHandle methods expect a variable number of arguments of typeObject. UsarObject… como argumento desativa a verificação de argumento estático.

Toda a verificação de argumentos é feita em tempo de execução. Além disso, métodos diferentes esperam ter um número diferente de argumentos de tipos diferentes.

Se não fornecermos um número adequado de argumentos com os tipos adequados, a chamada do método lançará umWrongMethodTypeException.

Por exemplo,get() espera pelo menos um argumento, o que ajuda a localizar a variável, masset() espera mais um argumento que é o valor a ser atribuído à variável.

5. Variável manipula modos de acesso

Geralmente, todos os métodos da classeVarHandle __ caem em cinco modos de acesso diferentes.

Vamos examinar cada um deles nas próximas subseções.

5.1. Acesso de leitura

Métodos com nível de acesso de leitura permitem obter o valor da variável sob os efeitos especificados de ordenação da memória. Existem vários métodos com este modo de acesso como:get(), getAcquire(), getVolatile()egetOpaque().

Podemos facilmente usar o métodoget() em nossoVarHandle:

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

O métodoget() leva apenasCoordinateTypes como parâmetros, então podemos simplesmente usarthis em nosso caso.

5.2. Acesso de gravação

Métodos com nível de acesso de gravação nos permitem definir o valor da variável sob efeitos específicos de ordenação da memória.

Da mesma forma que os métodos com acesso de leitura, temos vários métodos com acesso de gravação:set(), setOpaque(), setVolatile()esetRelease().

Podemos usar o métodoset() em nossoVarHandle:

publicIntHandle.set(this, 15);

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

O métodoset() espera pelo menos dois argumentos. O primeiro ajudará a localizar a variável, enquanto o segundo é o valor a ser definido para a variável.

5.3. Acesso de atualização atômica

Métodos com esse nível de acesso podem ser usados ​​para atualizar atomicamente o valor da variável.

Vamos usar o métodocompareAndSet() para ver os efeitos:

publicIntHandle.compareAndSet(this, 1, 100);

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

Além do métodoCoordinateTypes, t, elacompareAndSet() leva dois valores adicionais:oldValueenewValue. O método define o valor da variável se for igual aoldVariable ou folhas caso contrário, não mudou.

5.4. Numeric Atomic Update Access

Esses métodos permitem realizar operações numéricas comogetAndAdd () sob efeitos de ordenação de memória específicos.

Vamos ver como podemos realizar operações atômicas usando umVarHandle:

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

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

Aqui, o métodogetAndAdd() primeiro retorna o valor da variável e, em seguida, adiciona o valor fornecido.

5.5. Acesso à atualização atômica bit a bit

Os métodos com esse acesso permitem executar atomicamente operações bit a bit sob efeitos específicos de ordenação da memória.

Vejamos um exemplo de uso do métodogetAndBitwiseOr():

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

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

Este método obterá o valor da nossa variável e executará uma operação OR bit a bit nela.

A chamada do método lançará umIllegalAccessException se não corresponder ao modo de acesso exigido pelo método com o permitido pela variável.

Por exemplo, isso acontecerá se tentarmos usar um métodoset() em uma variávelfinal.

6. Efeitos de ordenação de memória

Mencionamos anteriormente queVarHandle methods allow access to variables under specific memory ordering effects.

Para a maioria dos métodos, existem 4 efeitos de ordenação da memória:

  • Plain leituras e gravações garantem atomicidade bit a bit para referências e primitivas abaixo de 32 bits. Além disso, eles não impõem restrições de ordem em relação aos outros traços.

  • Opaque operações são atômicas bit a bit e ordenadas de forma coerente em relação ao acesso à mesma variável.

  • As operaçõesAcquireeRelease obedecem às propriedadesOpaque. Além disso, as leituras deAcquire serão solicitadas apenas após corresponder às gravações no modoRelease.

  • Volatile operações são totalmente ordenadas em relação umas às outras.

É muito importante lembrar queaccess modes will override previous memory ordering effects. Isso significa que, por exemplo, se usarmosget(), será uma operação de leitura simples, mesmo se declararmos nossa variável comovolatile.

Por isso, os desenvolvedores devem ter extremo cuidado ao usar operaçõesVarHandle.

7. Conclusão

Neste tutorial, apresentamos identificadores de variáveis ​​e como usá-los.

Este tópico é bastante complicado, pois os identificadores variáveis ​​visam permitir manipulação de baixo nível e não devem ser usados ​​a menos que seja necessário.

Como sempre, as amostras de código estão disponíveisover on GitHub.