解決しなければならない基本的な問題が 2 つあります。1 つ目は、スレッドの衝突をどのように生成するかということです。次に、テストをどのように検証しますか?
最初のものは簡単です。大きなハンマーを使います。あるスレッドが別のスレッドをステップ実行するケースを検出できるコードを作成し、そのコードを 50 回程度、連続する 50 個のスレッドで実行します。これは、カウントダウン ラッチを使用して行うことができます。
public void testForThreadClash() {
final CountDownLatch latch = new CountDownLatch(1);
for (int i=0; i<50; ++i) {
Runnable runner = new Runnable() {
public void run() {
try {
latch.await();
testMethod();
} catch (InterruptedException ie) { }
}
}
new Thread(runner, "TestThread"+i).start();
}
// all threads are waiting on the latch.
latch.countDown(); // release the latch
// all threads are now running concurrently.
}
testMethod() は、同期されたブロックがなければ、一部のスレッドが別のスレッドのデータを踏む状況を生成する必要があります。このケースを検出して例外をスローできるはずです。(上記のコードはこれを行いません。例外はテスト スレッドの 1 つで生成される可能性が高く、メイン スレッドでそれを検出するメカニズムを組み込む必要がありますが、それは別の問題です。私はそのままにしました。簡単にするためにアウトしますが、2 番目のラッチを使用してこれを行うことができます。これにより、テストは例外で時期尚早にクリアできます。)
2 番目の質問はややこしいですが、簡単な解決策があります。問題はこれです: ハンマーが衝突を引き起こすことをどのように知ることができますか? もちろん、あなたのコードは衝突を防ぐためにブロックを同期しているので、質問に答えることができません。でも君ならできる。同期されたキーワードを削除するだけです。クラスをスレッドセーフにするのは synchronized キーワードであるため、それらを削除してテストを再実行するだけです。テストが有効な場合は、失敗します。
上記のコードを最初に書いたとき、それは無効であることが判明しました。衝突を見たことがない。したがって、(まだ) 有効なテストではありません。しかし、これで、2 番目の質問に対する明確な答えを得る方法がわかりました。間違った答えを得ていますが、テストをいじって、探している失敗を生成することができます。私がしたことは次のとおりです。テストを 100 回続けて実行しました。
for (int j=0; j<100; ++j) {
testForThreadClash();
}
これで、テストは約 20 回目の反復で確実に失敗しました。これにより、テストが有効であることを確認できます。これで、synchronized キーワードを復元してテストを再実行できるようになり、クラスがスレッドセーフであるかどうかが確実にわかります。