分かりやすいJava 9変数ハンドル
1. 前書き
Java 9は、開発者向けの多くの新しい便利な機能をもたらしました。
それらの1つは、変数ハンドルを表すjava.lang.invoke.VarHandle APIです。これについては、この記事で説明します。
2. 可変ハンドルとは何ですか?
通常、a variable handle is just a typed reference to a variable。 変数には、クラスの配列要素、インスタンス、または静的フィールドを指定できます。
VarHandleクラスは、特定の条件下で変数への書き込みおよび読み取りアクセスを提供します。
VarHandlesは不変であり、目に見える状態はありません。 さらに、それらをサブクラス化することはできません。
各VarHandleには:
-
ジェネリック型T。これは、このVarHandleで表されるすべての変数の型です。
-
このVarHandleによって参照される変数を見つけることができる座標式のタイプである座標タイプCTのリスト
座標タイプのリストは空の場合があります。
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);
このVarHandleのcoordinateTypesは空ではなく、VariableHandlesTestクラスという1つの要素を持っていることがわかります。
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);
ここでは、通常のlookup().よりも広いアクセス権を持つprivateLookupIn()メソッドを選択しました。これにより、private, publicまたはprotected変数にアクセスできるようになります。
Java 9以前は、この操作に相当するAPIは、Reflection APIのUnsafeクラス_ and the _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メソッドの呼び出し
Most of the VarHandle methods expect a variable number of arguments of typeObject.引数として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. 書き込みアクセス
書き込みアクセスレベルのメソッドを使用すると、特定のメモリ順序効果の下で変数の値を設定できます。
読み取りアクセスを持つメソッドと同様に、書き込みアクセスを持つメソッドがいくつかあります:set(), setOpaque(), setVolatile()およびsetRelease().
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, theとは別に、compareAndSet()メソッドは2つの追加値を取ります: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);
このメソッドは、変数の値を取得し、ビット単位のOR演算を実行します。
メソッドで必要なアクセスモードと変数で許可されているアクセスモードが一致しない場合、メソッド呼び出しはIllegalAccessExceptionをスローします。
たとえば、これは、final変数でset()メソッドを使用しようとした場合に発生します。
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で利用できます。