注意:重複していません。トピックをよくお読みください- https://stackoverflow.com/users/3448419/apangin 見積もり:
本当の問題は、コードが機能しないのに機能することがある理由です。この問題は、ラムダがなくても再現されます。これにより、JVMのバグがあるのではないかと思います。
https://stackoverflow.com/a/53709217/26743 のコメントで、コードが開始ごとに異なる動作をする理由を見つけようとしましたが、そのディスカッションの参加者からアドバイスがありました。別のトピックを作成します。
次のソースコードを考えてみましょう。
public class Test {
static {
System.out.println("static initializer: " + Thread.currentThread().getName());
final long SUM = IntStream.range(0, 5)
.parallel()
.mapToObj(i -> {
System.out.println("map: " + Thread.currentThread().getName() + " " + i);
return i;
})
.sum();
}
public static void main(String[] args) {
System.out.println("Finished");
}
}
時々(ほとんど常に)デッドロックにつながります。
出力の例:
static initializer: main
map: main 2
map: ForkJoinPool.commonPool-worker-3 4
map: ForkJoinPool.commonPool-worker-3 3
map: ForkJoinPool.commonPool-worker-2 0
しかし、時々それはうまく終了します(非常にまれです):
static initializer: main
map: main 2
map: main 3
map: ForkJoinPool.commonPool-worker-2 4
map: ForkJoinPool.commonPool-worker-1 1
map: ForkJoinPool.commonPool-worker-3 0
Finished
または
static initializer: main
map: main 2
map: ForkJoinPool.commonPool-worker-2 0
map: ForkJoinPool.commonPool-worker-1 1
map: ForkJoinPool.commonPool-worker-3 4
map: main 3
その振る舞いを説明してもらえますか?
TL; DRこれはHotSpotのバグです JDK-8215634
この問題は、レースがまったくない単純なテストケースで再現できます。
public class StaticInit {
static void staticTarget() {
System.out.println("Called from " + Thread.currentThread().getName());
}
static {
Runnable r = new Runnable() {
public void run() {
staticTarget();
}
};
r.run();
Thread thread2 = new Thread(r, "Thread-2");
thread2.start();
try { thread2.join(); } catch (Exception ignore) {}
System.out.println("Initialization complete");
}
public static void main(String[] args) {
}
}
これは従来の初期化デッドロックのように見えますが、HotSpotJVMはハングしません。代わりに、次のように出力します。
Called from main
Called from Thread-2
Initialization complete
JVMS§6.5invokestatic
バイトコードの実行時に
解決されたメソッドを宣言したクラスまたはインターフェイスは、そのクラスまたはインターフェイスがまだ初期化されていない場合に初期化されます
Thread-2
がstaticTarget
を呼び出すと、メインクラスStaticInit
は明らかに初期化されていません(静的初期化子がまだ実行されているため)。これは、Thread-2
が JVMS§5.5 で説明されているクラス初期化手順を起動する必要があることを意味します。この手順によると、
- CのClassオブジェクトが、他のスレッドによるCの初期化が進行中であることを示している場合は、LC)を解放し、進行中の初期化が完了したことが通知されるまで現在のスレッドをブロックします。
ただし、クラスがスレッドmain
による初期化の進行中であるにもかかわらず、Thread-2
はブロックされません。
私はOpenJ9とJETをテストしましたが、どちらも上記のテストでデッドロックが発生することが予想されます。
HotSpotも-Xcomp
モードでハングしますが、-Xint
または混合モードではハングしないのは興味深いことです。
インタプリタが最初にinvokestatic
バイトコードに遭遇すると、JVMランタイムを呼び出してメソッド参照を解決します。このプロセスの一部として、JVMは必要に応じてクラスを初期化します。正常に解決された後、解決されたメソッドは定数プールキャッシュエントリに保存されます。定数プールキャッシュは、解決された定数プール値を格納するHotSpot固有の構造です。
上記のテストでは、invokestatic
を呼び出すstaticTarget
バイトコードは、最初にmain
スレッドによって解決されます。クラスはすでに同じスレッドによって初期化されているため、インタープリターランタイムはクラスの初期化をスキップします。解決されたメソッドは、定数プールキャッシュに保存されます。次回Thread-2
が同じinvokestatic
を実行すると、インタプリタはバイトコードがすでに解決されていることを確認し、ランタイムを呼び出さずに定数プールキャッシュエントリを使用するため、クラスの初期化をスキップします。
getstatic
/putstatic
の同様のバグは、ずっと前に修正されました- JDK-449356 ですが、修正はinvokestatic
に影響しませんでした。この問題に対処するために、新しいバグを送信しました JDK-8215634 。
ハングするかどうかは、どのスレッドが最初に静的呼び出しを解決するかによって異なります。 main
スレッドの場合、プログラムはデッドロックなしで完了します。静的呼び出しがForkJoinPool
スレッドのいずれかによって解決されると、プログラムはハングします。
バグは 確認済み です。これは、次のリリースで修正されています:JDK 8u201、JDK 11.0.2、およびJDK12。