20211011のJavaに関する記事は7件です。

各ベンダーの Java バージョン情報

各ベンダーごとに、java.exe --version で表示されるバージョン情報をまとめました。 ベンダー 名称 バージョン情報 Amazon Corretto OpenJDK 64-Bit Server VM Corretto-17.0.0.35.1(build 17+35-LTS, mixed mode, sharing) BellSoft LibericaJDK OpenJDK 64-Bit Server VM(build 17+35-LTS, mixed mode, sharing) Eclipse Foundation Temurin OpenJDK 64-Bit Server VM Temurin-17+35(build 17+35, mixed mode, sharing) Microsoft Microsoft Build of OpenJDK OpenJDK 64-Bit Server VM Microsoft-26987(build 17+35, mixed mode, sharing) Azul Zulu OpenJDK 64-Bit Server VM Zulu17.28+13-CA(build 17+35-LTS, mixed mode, sharing) Oracle Java Java HotSpot(TM) 64-Bit Server VM(build 17+35-LTS-2724, mixed mode, sharing)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

各ベンダーごとの Java バージョン情報

各ベンダーごとに、java.exe --version で表示されるバージョン情報をまとめました。 ベンダー 名称 バージョン情報 Amazon Corretto OpenJDK 64-Bit Server VM Corretto-17.0.0.35.1(build 17+35-LTS, mixed mode, sharing) BellSoft LibericaJDK OpenJDK 64-Bit Server VM(build 17+35-LTS, mixed mode, sharing) Eclipse Foundation Temurin OpenJDK 64-Bit Server VM Temurin-17+35(build 17+35, mixed mode, sharing) Microsoft Microsoft Build of OpenJDK OpenJDK 64-Bit Server VM Microsoft-26987(build 17+35, mixed mode, sharing) Azul Zulu OpenJDK 64-Bit Server VM Zulu17.28+13-CA(build 17+35-LTS, mixed mode, sharing) Oracle OpenJDK OpenJDK 64-Bit Server VM(build 17+35-2724, mixed mode, sharing) Oracle Java Java HotSpot(TM) 64-Bit Server VM(build 17+35-LTS-2724, mixed mode, sharing) VM 情報 バージョン情報の mixed mode などの値は、実行時のVMオプションによって決まります。 (なので、java -Xint --version と実行すると表示が変化します) mixed mode JIT コンパイルとインタプリタを併用するモードになっていることを示しています。 VM オプション -Xint で、インタプリタモード(interpreted mode)に変更できます。 VM オプション -Xcomp で、JIT コンパイルモード(compiled mode)に変更できます。 sharing CDS (クラス・データ共有) が有効になっていることを示しています。 VM オプション -Xshare:off でオフにできます。 emulated-client C1 (クライアント・コンパイラ)のみを使用することを示しています。1 -XX:CompilationMode=quick-only を付けた際に表示されます。 java -XX:CompilationMode=quick-only -XX:+PrintCompilation --version と実行すると、C1しか使われていないことが分かります。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

各ベンダーごとの Java17 バージョン情報

各ベンダーごとに、java --version で表示されるバージョン情報をまとめました。 ベンダー 名称 バージョン情報 Amazon Corretto OpenJDK 64-Bit Server VM Corretto-17.0.0.35.1(build 17+35-LTS, mixed mode, sharing) BellSoft LibericaJDK OpenJDK 64-Bit Server VM(build 17+35-LTS, mixed mode, sharing) Eclipse Foundation Temurin OpenJDK 64-Bit Server VM Temurin-17+35(build 17+35, mixed mode, sharing) Microsoft Microsoft Build of OpenJDK OpenJDK 64-Bit Server VM Microsoft-26987(build 17+35, mixed mode, sharing) Azul Zulu OpenJDK 64-Bit Server VM Zulu17.28+13-CA(build 17+35-LTS, mixed mode, sharing) Oracle OpenJDK OpenJDK 64-Bit Server VM(build 17+35-2724, mixed mode, sharing) Oracle Java Java HotSpot(TM) 64-Bit Server VM(build 17+35-LTS-2724, mixed mode, sharing) VM 情報 バージョン情報の mixed mode などの値は、実行時のVMオプションによって決まります。 (なので、java -Xint --version と実行すると表示が変化します) mixed mode JIT コンパイルとインタプリタを併用するモードになっていることを示しています。 VM オプション -Xint で、インタプリタモード(interpreted mode)に変更できます。 VM オプション -Xcomp で、JIT コンパイルモード(compiled mode)に変更できます。 sharing CDS (クラス・データ共有) が有効になっていることを示しています。 VM オプション -Xshare:off でオフにできます。 emulated-client C1 (クライアント・コンパイラ)のみを使用することを示しています。1 -XX:CompilationMode=quick-only を付けた際に表示されます。 java -XX:CompilationMode=quick-only -XX:+PrintCompilation --version と実行すると、C1しか使われていないことが分かります。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Java]条件分岐について(初学者)

はじめに 本記事では、Javaの繰り返し分岐について記述します。 本日よりJavaの学習を始めました。 if文 例文(ifのみ) int i = 100; if (i == 100){ System.out.println("True"); } // 「True」と表示されます。 参考 データ型 以下参考サイトになります。 基本のデータ型 データ型 値 int 整数 boolean true or false double 小数点付きの数字 String 文字列 System.out.println 以下参照ください。 System.out.printlnとは System.out.println は標準出力へ引数に指定した値を出力する if+else文 もし、aでなければbという風に、bに対する分岐としてelseを使用します。 int i = 100; if (i > 110){ System.out.println("True"); } else { System.out.println("False"); } // 「False」と表示されます。 if+else if+else文 もし、~ならばaで、そうでなくとも、もし~ならばb(else if)で、 それ以外は、cに対する分岐として(else)を使用します。 int i = 100; if (i > 120){ System.out.println("iは120より大きい"); } else if (i > 110){ System.out.println("iは110より大きく、120以下"); } else { System.out.println("iは110以下") } // 「iは110以下」と表示されます。 switch文 条件の値がcaseと一致する場合の処理が実行されます。 int i = 100; switch (i % 3){ case 0: //ここは「:」 System.out.println("3の倍数です"); break; case 1: //3で割った余りが「1」ということ System.out.println("3の倍数に1加えた数字です"); break; case 2: System.out.println("3の倍数に2加えた数字です"); break; // 「3の倍数に1加えた数字です」と表示されます。 break switch文を終了させる命令です。 int i = 100; switch (i % 3){ case 0: //ここは「:」 System.out.println("3の倍数です"); //break; case 1: //3で割った余りが「1」ということ System.out.println("3の倍数に1加えた数字です"); //break; case 2: System.out.println("3の倍数に2加えた数字です"); break; // 「3の倍数です」 // 「3の倍数に1加えた数字です」 // 「3の倍数に2加えた数字です」と連続で表示されてしまいます。 default どのcaseにも、一致しなかった時に実行する処理です。 int i = 100; switch (i % 30){ case 0: System.out.println("30の倍数です"); break; case 1: System.out.println("30の倍数に1加えた数字です"); break; case 2: System.out.println("30の倍数に2加えた数字です"); break; default: //ここも「:」 System.out.println("該当しませんでした"); break; // 「該当しませんでした」と表示されます。 以上です。 終わりに 私は、Rubyを学習していましたが、 考え方が似ている(?)のかJavaの学習がしやすいです。 基礎の基礎で、 学習を始めたてなので、応用じみた投稿ではありませんが、 コツコツ頑張ります!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Processingを扱う

はじめに この記事ではJavaベースの言語、Processingを扱っていきます。Processingはデジタルアートを作成するための言語のようです。Processingホームページよりダウンロード可能です。 基本 Javaなのでvoidを使用するべきですが、まずは基本ということで基本の書き方を紹介します。 スケッチブックサイズの指定 サイズの指定はsize()を使用します。これを書くことでそのスケッチブックに描画することができます。 書き方:size(x,y); Javaですから、命令分の終わりには;(セミコロン)をつけましょう。 size(500,500); 四角形を描画する 四角形を描画するにはrect()を使用します。 書き方:rect(x,y,width,height); size(500,500); rect(0,0,100,100); 円の描画 円を描画するにはellipse()を使用します。 書き方:ellipse(x,y,width,height); size(500,500); ellipse(250,250,100,100); 色の着色 図形に色を着色するにはfill()、着色しない場合はnoFill()を使用します。 書き方:fill(R,G,B); 書き方:noFill(); 背景に色をつける場合はbackground()を使用します。 書き方:background(R,G,B); 図形の線の変更 図形の線(以下、線)の設定方法。 線をなくす:noStroke(); 線の幅を変更:strokeWeight(Px); 線の色を変更:stroke(R,G,B); voidを使用する setupとdraw setup関数は起動時に一回呼び出され、size()やstroke()、background()など設定を行う関数です。 draw関数は起動して、何回も呼び出される関数です。ここでrect()、ellipse()などの描画をを行います。 void setup() { size(500,500); fill(255,50,0); noStroke(); smooth(); //図形をなめらかに } void draw() { rect(0,0,100,100); ellipse(250,250,100,100); } 実行結果: 作品を作る マウスについてく円 ここでは上記に加え、mouseXとmouseYを使用します。 mouseXにはmouseのX_pos、mouseYにはmouseのY_posが入っています。 mouse_ellipse.pde void setup() { size(600,400); smooth(); fill(127,255,212); //aquamarine #7fffd4 } void draw() { background(0); //毎回背景を黒にして、重ならないようにする ellipse(mouseX,mouseY,40,40); } 実行結果: マウスが見えませんが、マウスについていきます。 カラフルな作品 random()関数を使用してきれいな模様を作ります。 新たにでてくるもの: random():第一引数未満の適当な数を選んで変数に代入します。 fill(r,g,b,a);r,g,bは「色の着色」で学びましたが、第四引数にa(アルファ値)を入れられます。 float:説明いらないと思いますが、32bitの浮動小数の値を表します。 void setup() { size(800,600); smooth(); noStroke(); background(0); } void draw() { float r1 = random(256); float g1 = random(256); float b1 = random(256); float a1 = random(256); float x1 = random(width); float y1 = random(height); float r2 = random(256); float g2 = random(256); float b2 = random(256); float a2 = random(256); float x2 = random(width); float y2 = random(height); float dia = random(20); fill(r1,g1,b1,a1); rect(x1,y1,dia,dia); fill(r2,g2,b2,a2); ellipse(x2,y2,dia,dia); } 説明ですがr1~a1,r2~a2の変数に色の情報を入れ、dia変数に大きさをいれます。背景は黒、綺麗な四角形と円を描画します。 おわりに Processingではゲームを作ったり、OpenGLを使用することもできるようです。ちなみにOpenGLの使用方法は import processing.opengl.* です。 三角形なども書く関数があるのでチートシートなどを参考にすると良いでしょう。 次はforやwhile,ifなどを使用して作品作りをします。 おつかれさまでした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アノテーションの基礎知識(Java)

はじめに SpringやDomaを使っているとよく出てくるアノテーションですが、正確な役割などは あまり理解せず、フィーリングで使ってきていたので基礎から立ち返りたいと思います。 アノテーションとは? @Overrideのように「@ + 名前」のような形式で表現されるものを アノテーションと呼びます。 これらをメソッドやクラスなどjavaのあらゆる機能に付けることで利用します。 アノテーションには以下のような役割があります。 クラスやメソッドなどに対して補足的な情報を付け加えるためのもの。 このように、あくまで補足的な情報を付け加えるためのものなので、アノテーションそれ自体に なにか機能が含まれているわけではありません。 いわば、ソースコードのコメントのようなものです。 したがって、それだけでは処理的には何の意味もありません。 アノテーションが必要な理由 それならば、どうしてアノテーションを付ける必要があるのでしょうか? それはアノテーションと特定の機能を組み合わせることで、強力な機能を実現することができるからです。 コンパイラなどと組み合わさることで、通常では検出できないようなエラーやバグを探知する。また、逆にコンパイルによる特定の警告を抑制することもできる。(例:@Override、@SuppressWarnings) アノテーションを元にして、コンパイル時に新しいクラスを生成できる。 リフレクションAPI(実行時の型情報を取得する機能)を用いることで、特定のアノテーションを対象にして処理を行うことができる。 アノテーションの利用方法 アノテーションの呼び出し方 基本的には以下のような形式で呼び出すことができます。 @SomeName あるいは @SomeName(パラメーター) このような形式で利用対象に情報を追加できます。 例えば、以下のアノテーションではvalueでリクエスト先のURLを定義しています。 // ~/sampleからリクエストが送信された場合に以下のメソッドを実行する。 @RequestMapping(value = "/sample") public String method() { return "index"; } なお、パラメーターを指定しなかった場合はアノテーションクラスで定義されているデフォルト値が 自動で設定されます。 また、アノテーションクラスでパラメーターに対してデフォルト値が定義されていなかった場合は、利用側でのパラメーター設定が必須となります。 具体例 @SuppressWarnings コンパイラによる警告を抑制するために使用されるアノテーションです。 基本的には警告が出ないように書くのがベストなのですが、特定のケースにおいては警告が 不適切な場合があるので、そのような場合に利用します。 パラメーターに抑制したい警告の名称を与えることによって、対象の警告を抑制することができます。 // raw型は使用する型が限定されていないため、型変換時にコンパイラが型チェックを行えず、警告が出力される。 void foo(List inputList) { // 対象となる処理にアノテーションをつけることで、警告が抑制される。 @SuppressWarnings("unchecked") List<String> list = (List<String>) inputList; } 補足:@SuppressWarningsのパラメーター一覧は以下のコマンドを実行することで出力される。 javac -X @Override 基本的には親クラスのメソッドをオーバーライドする際に利用するアノテーションですが、 実際にはこのアノテーションがなくてもオーバーライドすることはできます。 しかし、このアノテーションをつけることでオーバーライドした際に親クラスのメソッドと定義が異なっていた場合にコンパイラがコンパイルエラーとして出力してくれるので間違えることなく、オーバーライドを進めることができます。 Anima.java public class Animal { protected void eat(int num, String food) { System.out.println("Animal eats" + num + food); } } Dog.java public class Dog extends Animal { @Override // NG(引数の数が足りないため、コンパイルエラーとなる。) protected void eat(int num) { System.out.println("Dog bites" + num); } @Override // NG(引数の型が一致しないため、コンパイルエラーとなる。) protected void eat(int num, int food) { System.out.println("Dog bites" + num + food); } @Override // OK protected void eat(int num, String food) { super.eat(num, food); System.out.println("Dog bites" + num + food); } } アノテーション作成方法 アノテーションを自作する際には以下の点に注意してください。 すべてのアノテーションクラスはjava.lang.Annotationクラスを明示的に継承する。 @Target,@Retentionなどのメタアノテーションに関しては、明記されてない場合、デフォルト値が設定される。(メタアノテーションに関しては後述) パラメーターはインターフェイスのメソッドと同じように定義する。 パラメーターにはdefaultキーワードを用いて、デフォルト値を設定できる。 デフォルト値を設定しない場合、必須パラメーターとなる。 // アクセス修飾子 @interface インターフェース名で定義する。 @Target(ElementType.METHOD) @Retention(RetentinoPolicy.SOURCE) public @interface hoge (extends Annotation) { int num(); String value() default ""; } アノテーションの種類 メタアノテーションについて アノテーションに付けて利用するアノテーションをメタアノテーションと呼びます。 メタアノテーションとして代表的なものとしては、以下のようなものがあります。 @Retention @Target これらは基本的にどんなアノテーションにも付いており、省略された場合は デフォルト値が設定されるようになっています。 @Retention retentionは保持という意味を持つように、アノテーションの有効期限を設定することができます。 有効期限は、RetentionPolicy型のenum値として以下の3つが定義されています。 SOURCE:ソースコードでのみ有効であり、コンパイル時には削除される。(例:@Override、@SuppressWarnings:コンパイラやIDEに対して、エラーを表示させるようにしたり、警告を抑制させたりする。) CLASS:コンパイラにより生成されたクラスファイルに記録される。JVMによる実行時には削除されるので、リフレクションを用いても情報を取得することはできない。(例:@AutoValue→開発者が作成したコードを分析して、新しいクラスを生成する。) RUNTIME:クラスファイルにも記録され、かつ実行時にJVMにも認識してもらう。基本的にリフレクション目的で使用する場合に設定する必要がある。(例:@Deprecated) なお、@Retentionを省略した場合のデフォルト値はRetentionPolicy.CLASSとなります。 @Target アノテーションが適用可能なコンテキストを示すメタアノテーションです。 例えば、@Target(ElementType.METHOD)の場合、対象のアノテーションはメソッドのみに適用可能です。 なお、コンテキストはenum型のElementTypeクラスで定義されており、以下のような値を持ちます。 METHOD:メソッド FIELD:フィールド PARAMETER:関数のパラメーター PACKAGE:パッケージ CONSTRUCTOR:コンストラクタ TYPE:変数の型 etc. また、コンテキストは複数指定することも可能です。 @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) なお、@Targetを省略した場合、タイプパラメーターを除くすべてのコンテキストで適用可能なアノテーションと認識されます。 参考記事 unchecked warningの意味 アノテーション作成方法 ちょっと特殊なJavaのアノテーション
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JEP 193: Variable Handles について

Java 9 で Variable Handles と呼ばれる新しい API が追加されました。これは Java 7 の MethodHandle を拡張し、クラス java.lang.invoke.VarHandle でフィールドの変数や配列に対して強い型付けを持って参照ができます。ただし変数の参照だけでなく、Atomic 操作やリフレクションなどの操作に利用可能です。 例えば、ある一つの変数に対してアトミック操作を行うことによって、並列プログラミング時に変数に対するアクセスを保護を行うことができます。 これまでは、アトミック関連クラスでこれらの操作を行ってきましたが、これからは、Variable Handles を使用することができます。 また、リフレクションの代わりに使用することもできます。リフレクション API と比較した場合、優れたパフォーマンスを発揮し、型安全性を持たせることができます。 つまり、Variable Handles はパフォーマンスが求められる場面における、リフレクションの代替として、もしくはアトミック操作を利用する場面において利用を検討することができます。 アトミック操作(不可分操作)とは 途中に割り込みが入らない操作の事で、例えば、銀行口座の入金や出金の処理では、金額の不一致を防ぐために排他制御が必要です。例えば、排他制御を行いアトミック性を確保します。 NOTE: Java で、変数に volatile キーワードをつけても Atomic にはならず排他制御には利用できません。 Variable Handles は、C/C++11 Atomic と互換性を持つように設計されています。今回の VarHandle を実装せずに、従来のAtomic API に対して修正を加えた場合、C/C++ 11 で追加されたメモリモデルを利用するために、追加のアクセス整合性ポリシーが必要となり、実装が複雑化する可能性がありました。それを避けるために Variable Handles を導入しています C/C++11: 2011年に見直されたC言語の仕様の規格: C/C++11のメモリの取得と解放の詳細については、 https://cpprefjp.github.io/reference/atomic.html https://cpprefjp.github.io/reference/atomic/memory_order.html Variable Handle の目的 Variable Handle は下記の4点を考慮して提供される API です。 安全性: JVM を壊さないように メモリを扱う 整合性: フィールドへのアクセスは、getfield および putfield バイト・コードと同じアクセス規則に従う (final は操作不可) パフォーマンス: sun.misc.Unsafe と同等のパフォーマンスを提供 ユーザビリティ: sun.misc.Unsafe よりも優れた API を提供 並列処理プログラミングの需要が増える一方で、今まで、クラスの変数(フィールド)に対する操作を、アトミック操作や順序付けされた操作として、うまく実装できない課題がありました。 これまで、これらを実現する為に、synchronized を使用するか、 java.util.concurrent.atomic を使用してきました。しかし、パフォーマンスが悪かったり、処理に対するオーバヘッドが大きいため、よりパフォーマンスを求めるために、sun.misc.Unsafe という、非推奨で非サポートの JVM の組み込み関数が使用されてきました(Java 1.5 以降では、java.util.concurrent.atomic 配下のクラスで内部的に Unsafe を利用)。 特に、Unsafe の組み込み関数は高速であるため、非推奨であるにもかかわらず、フレームワークやライブラリ等で幅広く利用され、結果として移植性や安全性が損なわれていました。 現在の実装の問題点 JEP 193: Variable Handles のオーナーの Paul Sandoz によると現在の問題点を、下記のように説明しています。 * Atomic* classes have overhead • Not used in j.u.concurrent classes * sun.misc.Unsafe is... unsafe, not “portable”, and going away • CAS is too important to relegate * No unified/safe model for accessing on and off-heap 訳すと下記の通りです。 Atomic 関連のクラスは、オーバヘッドが高い パフォーマンスが悪いため Atomic 関連クラスを java.util.concurrent クラスの中で使用していない sun.misc.Unsafe は 安全でなく、ポータビリティもない、そして今後取り除かれる。 CAS(Copy and Set)は移管するのにとても重要 Heap に対してアクセスするための統合的で安全なモデルがない sun.misc.Unsafe を利用した実装例(非推奨) sun.misc.Unsafe を使用した変数を Atomic で書き換える例を下記に記載します。 MyCounter のコンストラクターで counter のメモリ・アドレス の offset 値を取得しています。incrementAndGet() メソッドで、この offset 値を使用して変数(フィールド)の値を更新します。 書き換える対象の変数は、値を読み書きするすべてのスレッドから見えるように、volatile として宣言する必要があります。(JLS 8.3.1.4. volatile Fields) incrementAndGet()メソッドでは while ループを使用し、成功するまで操作を繰り返し行います。 実際には compareAndSwapLong() を呼び出し、変数が記載されているメモリのオフセットを指定し、現在の値と比較したのち、以前の値を1増加しています。そして更新したのち、以前の値が変更されたかどうかを確認しています。ここではブロッキング処理は実装していないため、高速にクラス内のインスタンス変数を書き換えることができています。 しかし、型安全性がないため Java 言語の観点では推奨された実装方法ではありません。 import java.util.stream.IntStream; import java.lang.reflect.Field; import sun.misc.Unsafe; class MyCounter { private Unsafe unsafe; private volatile long counter = 0; private long valueOffset; public MyCounter() throws IllegalAccessException, NoSuchFieldException { unsafe = getUnsafe(); valueOffset = unsafe.objectFieldOffset(MyCounter.class.getDeclaredField("counter")); } public static void main(String... argv){ try{ MyCounter main = new MyCounter(); IntStream.range(0,100) .forEach(num -> { //実際にはここが並列処理として実行される System.out.println(main.incrementAndGet()); }); }catch (IllegalAccessException | NoSuchFieldException e){ e.printStackTrace(); } } private Unsafe getUnsafe() throws IllegalAccessException, NoSuchFieldException { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); return (Unsafe) f.get(null); } public long incrementAndGet() { long current = counter; long next = current + 1; while (!unsafe.compareAndSwapLong(this, valueOffset, current, next)) { counter = next; } return counter; } } Java 11 移行の sun.misc.Unsafe の扱い sun.misc.Unsafe は Java11 から非サポートになります。 java.util.concurrent.atomic パッケージ配下の Atomic* 関連クラスも、将来的に VarHandles で書き換えられるようです。 実際に、Java 17 の java.util.concurrent.atomic.AtomicInteger のソースコードを確認した所、下記のようにコメントが記載されていました。これによると、現時点では、まだ起動時の依存関係で未解決の問題があるため Unsafe を利用しているようですが、将来的には書き換えられると思われます。 import jdk.internal.misc.Unsafe; /* * This class intended to be implemented using VarHandles, but there * are unresolved cyclic startup dependencies. */ private static final Unsafe U = Unsafe.getUnsafe(); private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value"); Java 11 で危険を承知で利用したい場合は、module-info.java に require を記載する等の方法で回避できますが 基本的には非推奨なため、ここではその詳細については割愛します。 java.lang.invoke.VarHandle の利用方法 VarHandle を利用するためには、下記のクラスを利用します。 クラス java.lang.invoke.VarHandle サンプルコード 複数のスレッドから変更されるインスタンス変数(フィールド)を持つクラスを定義します。 import java.nio.ByteBuffer; public class Data { public int counter = 1; private int privateField = 10; public String name = "Yoshio Terada"; public byte[] data = new byte[]{1, 0, 0, 0, 1, 0, 0, 0}; public char[] charArray = new char[]{'A','B','C','D','E','F'}; public ByteBuffer dataBuffer = ByteBuffer.wrap(this.data); } VarHandle を利用して読み書きをするコード例を下記に示します。ここでは最初に ATOMIC 操作ではない API を利用しています。 public void nonAtomicGetAndSetEvaluation(Data data){ try { VarHandle varHandle = MethodHandles.lookup().findVarHandle(Data.class,"counter", int.class); Data data = new Data(); //Read Access System.out.println(varHandle.get(data)); //アクセスモード: GET System.out.println(varHandle.getVolatile(data)); //アクセスモード: GET_VOLATILE System.out.println(varHandle.getOpaque(data)); //アクセスモード: GET_OPAQUE System.out.println(varHandle.getAcquire(data)); //アクセスモード: GET_ACQUIRE //Write Access int newValue = 2; varHandle.set(data, newValue); //アクセスモード: SET System.out.println(varHandle.get(data)); //アクセスモード: GET varHandle.setVolatile(data, newValue + 1); //アクセスモード: SET_VOLATILE System.out.println(varHandle.get(data)); varHandle.setOpaque(data, newValue + 2); //アクセスモード: SET_OPAQUE System.out.println(varHandle.get(data)); varHandle.setRelease(data, newValue + 3); //アクセスモード: SET_RELEASE System.out.println(varHandle.get(data)); } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } } public void arrayCheck(Data data) { VarHandle byteArrayViewVarHandle = MethodHandles.arrayElementVarHandle(char[].class); // char 配列 {'A','B','C','D','E','F'} より char を一つづつ取得 for (int i = 0; i < data.charArray.length; i++) { var out = byteArrayViewVarHandle.getAcquire(data.charArray, i); System.out.println(out); // char が 'C' , 'D' ならば 'Z' に置き換え if (out.equals('C')|| out.equals('D')) { byteArrayViewVarHandle.setRelease(data.charArray, i, 'Z'); } } // char 配列は {'A','B','Z','Z','E','F'} に置き換わる } ソースコードの説明 VarHandle オブジェクトは、MethodHandles.Lookup クラスのファクトリ・メソッドを使用して作成できます。 Lookup のファクトリから VarHandle を生成するために3種類のメソッドが用意されています。インスタンス変数、クラス変数用、そしてリフレクションの代わりに利用可能な VarHandle を生成することができます。 findStaticVarHandle​(Class<?> decl, String name, Class<?> type) VarHandle staticVarHandle = MethodHandles.lookup() .findStaticVarHandle(Hello.class, "staticConter", int.class); findVarHandle​(Class<?> recv, String name, Class<?> type) VarHandle varHandle = MethodHandles.lookup() .findVarHandle(Hello.class, "count", int.class); unreflectVarHandle​(Field f) VarHandle unreflectVarHandle = MethodHandles .lookup() .unreflectVarHandle(Hello.class.getDeclaredField("count")); アクセスモード VarHandle は異なるアクセスモードで変数への読み取り、書き込みのアクセスをサポートしています。 列挙型 VarHandle.AccessMode に31 個のアクセスモードが定義されており、AccessMode ごとに、VarHandle 内に対応メソッドがあります。たとえば、アクセス モード GET_AND_ADD の場合、VarHandle.getAndAdd() メソッドを使用します。 上記のサンプルコードでは、ATOMIC ではないアクセス・モードを利用しています。 ATOMIC ではない読み書き用のアクセス・タイプと対応するメソッド アクセスモード 対応するメソッド  ATOMIC か否か GET get() NON ATOMIC GET_ACQUIRE getAcquire() NON ATOMIC GET_OPAQUE getOpaque() NON ATOMIC GET_VOLATILE getVolatile() NON ATOMIC SET set() NON ATOMIC SET_OPAQUE setOpaque() NON ATOMIC SET_RELEASE setRelease() NON ATOMIC SET_VOLATILE setVolatile() NON ATOMIC ATOMIC で読み書きをするアクセスタイプと対応するメソッドを下記に示します。 アトミックで比較・設定する更新アクセスモード アクセスモード 対応するメソッド  ATOMIC か否か COMPARE_AND_EXCHANGE compareAndExchange() ATOMIC COMPARE_AND_EXCHANGE_ACQUIRE compareAndExchangeAcquire() ATOMIC COMPARE_AND_EXCHANGE_RELEASE compareAndExchangeRelease() ATOMIC COMPARE_AND_SET compareAndSet() ATOMIC GET_AND_SET getAndSet() ATOMIC GET_AND_SET_ACQUIRE getAndSetAcquire() ATOMIC GET_AND_SET_RELEASE getAndSetRelease() ATOMIC WEAK_COMPARE_AND_SET weakCompareAndSet() できる限り(Possibly) ATOMIC WEAK_COMPARE_AND_SET_ACQUIRE weakCompareAndSetAcquire() できる限り(Possibly) ATOMIC WEAK_COMPARE_AND_SET_PLAIN weakCompareAndSetPlain() できる限り(Possibly) ATOMIC WEAK_COMPARE_AND_SET_RELEASE weakCompareAndSetRelease() できる限り(Possibly) ATOMIC 数値用のアトミック更新アクセスモード アクセスモード 対応するメソッド  ATOMIC か否か GET_AND_ADD getAndAdd() ATOMIC GET_AND_ADD_ACQUIRE getAndAddAcquire() ATOMIC GET_AND_ADD_RELEASE getAndAddRelease() ATOMIC ビット単位でのアトミック更新アクセスモード アクセスモード 対応するメソッド  ATOMIC か否か GET_AND_BITWISE_AND getAndBitwiseAnd() ATOMIC GET_AND_BITWISE_AND_ACQUIRE getAndBitwiseAndAcquire() ATOMIC GET_AND_BITWISE_AND_RELEASE getAndBitwiseAndRelease() ATOMIC GET_AND_BITWISE_OR getAndBitwiseOr() ATOMIC GET_AND_BITWISE_OR_ACQUIRE getAndBitwiseOrAcquire() ATOMIC GET_AND_BITWISE_OR_RELEASE getAndBitwiseOrRelease() ATOMIC GET_AND_BITWISE_XOR getAndBitwiseXor() ATOMIC GET_AND_BITWISE_XOR_ACQUIRE getAndBitwiseXorAcquire() ATOMIC GET_AND_BITWISE_XOR_RELEASE getAndBitwiseXorRelease ATOMIC Atomic での更新サンプル 上記のアクセスモードの内 ATOMIC 操作が可能なメソッドを利用して実装を行います。数値用、ビット単位、比較をして更新するメソッドが用意されているため、必要に応じたメソッドを呼び出します。 public void atomicUpdate(Data data){ try { VarHandle varHandle = MethodHandles.lookup().findVarHandle(Data.class, "name", String.class); String expectedValue = "Yoshio Terada"; String newValue = "I Love Duke!!"; // compareAndSet() は比較・更新に成功したか否かを真偽値で返す (ここでは true が表示、値は I Love Duke に変更) System.out.println(varHandle.compareAndSet(data, expectedValue, newValue)); // compareAndExchangeAcquire は読み込み時にメモリをバリアし比較・更新したい場合に利用、返り値は変更前の値(ここでは I Love Duke が表示、値は Yoshio Teradaに変更) System.out.println(varHandle.compareAndExchangeAcquire(data, newValue, expectedValue)); // compareAndExchangeRelease は書き込み時にメモリをバリアし比較・更新したい場合に利用、返り値は変更前の値(ここでは Yoshio Terada が表示、値は I Love Dukeに変更) System.out.println(varHandle.compareAndExchangeRelease(data, expectedValue, newValue)); // getAndSetAcquire は読み込み時にメモリをバリアし更新したい場合に利用、返り値は変更前の値(ここでは I Love Duke が表示、値は Yoshio Teradaに変更) System.out.println(varHandle.getAndSetAcquire(data, expectedValue)); //最後に Yoshio Terada が表示される System.out.println(data.name); } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } } public void numericAtomicUpdate (Data data) { try { VarHandle varHandle = MethodHandles.lookup().findVarHandle(Data.class, "counter", int.class); final int adder = 1; //読み込み時にメモリをバリアしたい場合 Acquire を利用、返り値は変更前の値(ここでは1が表示) System.out.println(varHandle.getAndAddAcquire(data,adder)); //書き込み時にメモリをバリアしたい場合 Release を利用 返り値は変更前の値 (ここでは2が表示) System.out.println(varHandle.getAndAddRelease(data,adder)); System.out.println(data.counter); // (ここで3が表示) } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } } リフレクションの代わりに Variable Handles を利用する 最後に、リフレクション API を利用する代わりに、VarHandle を利用する方法を紹介します。 ここでは、private フィールドにアクセスし、その値を確認したり、更新するサンプルを下記に紹介します。MethodHandles のルックアップで unreflectVarHandle() を呼び出して VarHandle のインスタンスを生成します。 そして、get(), set() などのメソッドを呼び出して値を更新しています。 public class Data { private int privateField = 10; } import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.lang.reflect.Field; public class DataUpdater { public static void main(String... argv) { DataUpdater main = new DataUpdater(); Data data = new Data(); main.insteadOfReflectionTest(data); } public void insteadOfReflectionTest() { public void insteadOfReflectionTest(Data data) { try { Class<? extends Data> class1 = data.getClass(); Field privateField = class1.getDeclaredField("privateField"); // private フィールドにアクセスするための VarHandle を取得 VarHandle unreflectVarHandle = MethodHandles.privateLookupIn(class1, MethodHandles.lookup()) .unreflectVarHandle(privateField); // private フィールドの現在値を取得 10 を出力 System.out.println(unreflectVarHandle.get(data)); // private フィールドを新しい値で更新 unreflectVarHandle.set(data, 20); // private フィールドの現在値を取得 20 を出力 System.out.println(unreflectVarHandle.get(data)); } catch (NoSuchFieldException | SecurityException | IllegalAccessException e) { e.printStackTrace(); } } } メモリ・フェンス 今回、詳しく取り上げていませんが、VarHandle は C++11 の atomic_thread_fence に対応したメモリの順番を制御するためのメモリ・フェンスの機能も持っています。 フェンス操作は、メモリ順序をきめ細かく制御するための最小限の機能を提要しています。VarHandle では、異なるフェンスを作成するために 5 つの static メソッドを提供しています。 フェンスの強弱 loadLoadFence fence(弱) < acquire fence(中) < full fence(強) storeStoreFence fence(弱) < release fence(中) < full fence(強) Method フェンス前の操作 フェンス後の操作 C++ 11 atomic_thread_fence との対応 意味 fullFence() loads and stores loads and stores memory_order_seq_cst フェンス前のロードとストアが、フェンス後、ロードとストアで並べ替えられないようにする acquireFence() loads loads and stores memory_order_acquire フェンス前のロードがフェンス後にロードおよびストアで並べ替えられないようにする releaseFence() loads and stores stores memory_order_release フェンス前のロードとストアがフェンス後にストアで並び替えられないようにする loadLoadFence() loads loads フェンス前のロードがフェンス後のロードと並び替えられないようにする storeStoreFence() stores stores フェンス前のストアがフェンス後のストアと並べ替えられないようにする まとめ 上記のサンプル・コードで示したように、VarHandle はアトミック操作やリフレクションの代わりに、利用できることがわかりました。アトミック関連クラスは、利用する際にオーバヘッドが高く、sun.misc.Unsafe はハイ・パフォーマンスではあるものの、非推奨のクラスでした。こうした課題を解決するために提供された VarHandle はパフォーマンスが求められる場面において利用が可能です。 利用する上では、いくつかの注意点があります。そこで実際に使用する際には API ドキュメントを注意深く読んで、理解してお使いください。 注意点を理解した上でご利用いただく事で、推奨された方法で並列プログラミング時のパフォーマンスを向上させることも可能ですので、この記事を手始めにお試しください。 備考 VarHandle は既に Fork/Join 関連クラスで実際に sun.misc.Unsafe から書き換えられています。下記の ForkJoinPool の実装中では Fence の実際の使用例も記載されていますので、参照してください。 (参照:ForkJoinPool, ForkJoinTask) また現在、Incubator のプロジェクトとして位置づけされている JEP 412: Foreign Function & Memory API (Incubator) は実装で Variable Handle を利用して実装しています。 Foreign Function & Memory API のソースコードはこちら 参考 java.lang.invoke.VarHandle JEP 193: Variable Handles Using JDK 9 Memory Order Modes by Doug Lea. Acquire and Release Semantics TODO: 上記のサンプルをマルチ・スレッドのコードに書き直し、もう少し検証をしたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む