原文はこちら。
https://blogs.oracle.com/geertjan/entry/lock_contention_profiling_in_netbeans
ロック競合のプロファイリングは、マルチコア環境において非常に重要です。スレッドが別のスレッドが保持しているロックを取ろうとすると、ロック解除を待たざるを得ないため、ロック競合が発生します。ロックを使うことによってシーケンシャルな作業になるため、ロック競合により、並列に複数のコアを使用して得られる可能性のあるパフォーマンス向上を削がれます。最悪のシナリオでは、1つのスレッドしか動作しません。NetBeans 8には、NetBeans Profilerで新たにロックの競合、つまり、パフォーマンスのボトルネックに関する詳細情報を表示するようになりました。これを使うことで、問題のスレッドを識別しやすくなります。
例として、Java Tutorialにあるデッドロックのサンプルコードを使い、NetBeans IDEのツールでコードを識別・分析してみましょう。
Deadlock (The JavaTM Tutorials)
http://docs.oracle.com/javase/tutorial/essential/concurrency/deadlock.html
このデッドロックの表現がいいですね。
AlphonseとGastonは友人同士で、礼儀正しい者たちです。礼儀の厳格なルールは、友人にお辞儀をするときは、相手がお辞儀を返すタイミングがあるまで、ずっと
お辞儀をし続けなければならない、というものです。残念ながら、このルールは2人が同時にお互いに屈するかもしれないという可能性を考慮していません。
誰が初めにお辞儀をしたか、または少なくともお辞儀の順序を識別するために、ファイルを右クリックし、[ファイルをプロファイル(Profile file)]を選択します。Profile Task Managerで、以下のように選択します。
[実行]をクリックすると、スレッドウィンドウが開き、2個のスレッドがブロックされている、つまり、同期メソッドに入ったり、ブロックしようとしている関連するスレッドがブロックされていることを赤い「モニター」の線が知らせてくれています。
では、どちらのスレッドがロックを保持しているのでしょう?どちらがどちらにブロックされているのでしょう?上記の画面ではわかりませんね。
NetBeans 8の新機能で、新しいロック競合ウィンドウを使い、デッドロックを分析し、どちらのスレッドがロックしているのかを究明することができるようになっています。
(訳注)
NetBeansの日本語UIでは「ロック内容」になっています。
以下はロックをシミュレートしたコードですが、ちょっとだけ手を入れています。それは、setNameメソッドをスレッドで使い、関連するNetBeansのツールで分析しやすくしています。また、匿名inner RunnablesをLambda式に変換しています。
package org.demo;
public class Deadlock {
static class Friend {
private final String name;
public Friend(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public synchronized void bow(Friend bower) {
System.out.format("%s: %s"
+ " has bowed to me!%n",
this.name, bower.getName());
bower.bowBack(this);
}
public synchronized void bowBack(Friend bower) {
System.out.format("%s: %s"
+ " has bowed back to me!%n",
this.name, bower.getName());
}
}
public static void main(String[] args) {
final Friend alphonse
= new Friend("Alphonse");
final Friend gaston
= new Friend("Gaston");
Thread t1 = new Thread(() -> {
alphonse.bow(gaston);
});
t1.setName("Alphonse bows to Gaston");
t1.start();
Thread t2 = new Thread(() -> {
gaston.bow(alphonse);
});
t2.setName("Gaston bows to Alphonse");
t2.start();
}
}
上記のコードでは、bowBack(お辞儀を返す)を起動しようとすると、両方のスレッドがブロックする可能性が非常に高いのです。各スレッドは他方のお辞儀が終了するのを待っているため、どちらのブロックも終了しません。
[注意]ご覧になっておわかりかと思いますが、コードでThreadを作成しているところであればどこでも、ThreadのsetNameメソッドを使って名前を付けておくべきでしょう。というのは、スレッドに名前を付けると、IDEのツールでより多くの意味を持つようになるからです。もし付けなかったとしたら、スレッドの順序をベースとした"thread-5"とか"thread-6"といった、無意味なスレッド名を使います。 (通常は、上記のような簡単なデモのシナリオ以外では、同じクラスでスレッドを開始しません。そのため、スレッドの開始順序を知らないので、Thread-5やThread-6が何を意味するのか全く見当がつかないことでしょう)。
もう少しコンパクトにすると、以下のような感じになります。
Thread t1 = new Thread(() -> {
alphonse.bow(gaston);
},"Alphonse bows to Gaston");
t1.start();
Thread t2 = new Thread(() -> {
gaston.bow(alphonse);
},"Gaston bows to Alphonse");
t2.start();