1前書き

Java 9は開発者に多くの新しい便利な機能をもたらしました。

そのうちの1つがhttps://docs.oracle.com/javase/9​​/docs/api/java/lang/invoke/VarHandle.html[

java.lang.invoke.VarHandle

]APIです。この記事で探求するつもりです。

** 2変数ハンドルとは

一般的に、

変数ハンドルは単に

変数への型付き参照です。

変数は、クラスの配列要素、インスタンス、または静的フィールドです。


  • VarHandle

    クラスは、特定の条件下で変数への書き込みおよび読み取りアクセスを提供します。


VarHandles

は不変で、可視状態はありません。さらに、それらをサブクラス化することはできません。



VarHandle

には、次のものがあります。

  • 総称型

    T

    。これは、で表されるすべての変数の型です。

この

VarHandle

** 座標タイプのリストである

CT

の座標タイプのリスト

この

VarHandle

によって参照される変数を見つけることを可能にする式

座標タイプのリストは空の場合があります。


VarHandle

の目的は、フィールドおよび配列要素に対して




java.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. パブリック変数の変数ハンドル

  • これで、

    findVarHandle()メソッドを使用して、

    publicTestVariable



    VarHandle__を取得できます。

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

この

VarHandle



coordinateTypes

は空ではなく、1つの要素、つまり

VariableHandlesTest

クラスがあります。


3.2. プライベート変数の変数ハンドル

プライベートメンバがあり、そのような変数の変数ハンドルが必要な場合は、

privateLookupIn()

メソッドを使用してこれを取得できます。

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

ここでは、通常の

lookup()よりも幅広いアクセス権を持つ

privateLookupIn()__メソッドを選択しました。

Java 9より前のバージョンでは、この操作に対応するAPIは

Unsafe

クラス

__と

Reflection

APIの

setAccessible()メソッドでした。

ただし、この方法には欠点があります。たとえば、変数の特定のインスタンスに対してのみ機能します。

そのような場合は、

VarHandle

がより優れた、より速いソリューションです。


3.3. 配列の変数ハンドル

前の構文を使用して配列フィールドを取得できます。

ただし、特定の型の配列の

VarHandle

も取得できます。

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

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

このような

VarHandle

には

int



[]

の2つの座標型があり、これらは

int

プリミティブの配列を表しています。


4

VarHandle

メソッドを呼び出す

  • ほとんどの

    VarHandle

    メソッドは、




    Object型の可変数の引数を期待します。**

    Object …​

    を引数として使用すると、静的引数チェックが無効になります。

すべての引数チェックは実行時に行われます。また、異なるメソッドは異なるタイプの異なる数の引数を持つことを期待します。

適切な型で適切な数の引数を渡さないと、メソッド呼び出しは__WrongMethodTypeExceptionをスローします。

たとえば、

get()

は少なくとも1つの引数を取ります。これは変数を見つけるのに役立ちますが、

set()

は変数に割り当てられる値であるもう1つの引数を取ります。


5変数ハンドルアクセスモード

通常、

VarHandle

クラスのすべてのメソッドは、5つの異なるアクセスモードに分類されます。

次のサブセクションでそれぞれを見ていきましょう。


5.1. 読み取りアクセス

読み取りアクセスレベルを持つメソッドは、指定されたメモリ順序付け効果の下で変数の値を取得することを可能にします。このアクセスモードには、

get()、getAcquire()、getVolatile()

、および__getOpaque()のようなメソッドがいくつかあります。


VarHandle



get()

メソッドを簡単に使用できます。

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


get()

メソッドは

CoordinateTypes

のみをパラメータとして取りますので、この場合は単に

this

を使用できます。


5.2. 書き込みアクセス

書き込みアクセスレベルを持つメソッドを使用すると、特定のメモリ順序付け効果の下で変数の値を設定できます。

読み取りアクセスを持つメソッドと同じように、書き込みアクセスを持つメソッドがいくつかあります。


VarHandle



set()

メソッドを使用できます。

publicIntHandle.set(this, 15);

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


set()

メソッドには少なくとも2つの引数が必要です。最初のものは変数を見つけるのに役立ちますが、2番目は変数に設定される値です。


5.3. アトミックアップデートアクセス

このアクセスレベルを持つメソッドは、変数の値を自動的に更新するために使用できます。

効果を確認するには、

compareAndSet()

メソッドを使用しましょう。

publicIntHandle.compareAndSet(this, 1, 100);

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


__CoordinateTypesとは別に、

compareAndSet()メソッドは2つの追加値を取ります。


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

このメソッドは変数の値を取得し、それに対してビットごとのOR演算を実行します。

  • メソッドが要求するアクセスモードと変数で許可されているアクセスモードが一致しない場合、メソッド呼び出しは

    IllegalAccessException

    をスローします。

たとえば、

final

変数で

set()

メソッドを使おうとすると、これが起こります。


6. メモリ順序付け効果


  • VarHandle

    メソッドは特定のメモリ順序付け効果のもとで変数にアクセスできることを先に述べました。

ほとんどの方法で、4つのメモリ順序付け効果があります。


  • Plain

    の読み書きは参照に対してビット単位のアトミック性を保証します

32ビット以下のプリミティブまた、それらは他の特性に関して順序付け制約を課しません。


  • Opaque

    操作はビット単位のアトミックで、コヒーレントに順序付けられ

同じ変数へのアクセスに関して。

  • Acquire

    および

    Release

    操作は、

    Opaque__プロパティに従います。また、


Require

モードの書き込みが一致した後でのみ、__獲得の読み取りが順序付けられます。

  • Volatile__操作は、互いに対して完全に順序付けされています。

  • アクセスモードは以前のメモリ順序付け効果** を上書きすることを覚えておくことは非常に重要です。これは、たとえば、

    get()

    を使用した場合、たとえ変数を

    volatile.

    として宣言した場合でも、単純な読み取り操作になることを意味します。

そのため、開発者は

VarHandle

操作を使用するときは細心の注意を払う必要があります。


7. 結論

このチュートリアルでは、変数ハンドルとその使い方を紹介しました。

変数ハンドルは低レベルの操作を可能にすることを目的としているため、このトピックは非常に複雑で、必要な場合以外は使用しないでください。

いつものように、コードサンプルはhttps://github.com/eugenp/tutorials/tree/master/core-java-9[GitHubで利用可能]です。