この壊れたダブルチェックロックはありますか?
-
11-07-2019 - |
質問
Checkstyleはこのコードを「ダブルチェックロックイディオムが壊れている」と報告しますが、実際にコードがダブルチェックロックの問題の影響を受けるとは思いません。
このIDを持つ行が存在しない場合、コードはデータベースに行を作成することになっています。マルチスレッド環境で実行され、主キーが存在するSQL例外を回避したい。
擬似コード:
private void createRow(int id) {
Row row = dao().fetch(id);
if (row == null) {
synchronized (TestClass.class) {
row = dao().fetch(id);
if (row == null) {
dao().create(id);
}
}
}
}
ダブルチェックロックのように見えることに同意できますが、静的変数を使用しておらず、fetch()およびcreate()のコードはおそらくインライン化して順序を乱すには複雑すぎます。
間違っているか、チェックスタイルですか? :)
解決
その最も内側の行を読みたいと仮定すると:
row = dao().create(id);
これは、 dao()。fetch
がcreateメソッドから適切にミューテックスされていることを前提とする、古典的なダブルチェックロックの問題ではありません。
編集:(コードが更新されました)
二重チェックロックの古典的な問題は、2つのスレッドが同じ値にアクセスする初期化が発生する前に値が割り当てられることです。
DAOが適切に同期され、部分的に初期化された値を返さないと仮定すると、これは二重チェックロックイディオムの欠陥の影響を受けません。
他のヒント
この場合、checkstyleは正しいと思います。提示されたコードでは、2つのスレッドの両方が同期ブロックへのエントリで row == null
を持っている場合にどうなるかを検討してください。スレッドAはブロックに入り、新しい行を挿入します。次に、スレッドAがブロックを出た後、スレッドBは(何が起きたのか分からないため)ブロックに入り、同じ新しい行をもう一度挿入しようとします。
コードを変更し、そこにかなり重要な行を追加しました。 new コードでは、2つのスレッドが共有(静的)変数への変更に依存しないため、これを回避できる場合があります。ただし、DBMSが INSERT OR UPDATE
などのステートメントをサポートしているかどうかを確認した方がよい場合があります。
この機能をDBMSに委任するもう1つの理由は、複数のアプリケーションサーバーを展開する必要がある場合です。 synchronized
ブロックはマシン間で機能しないため、その場合は別の操作を行う必要があります。
このようなコードを記述したい場合は、以下を考慮してください:
-
Java 1.4以降、メソッドの同期はかなり安価になりました。それは無料ではありませんが、ランタイムはデータ破損のリスクを負う価値があるほど実際にはそれほど苦しみません。
-
Java 1.5以降、Atomic *クラスがあり、アトミックな方法でフィールドの読み取りと設定ができます。残念ながら、彼らはあなたの問題を解決しません。 AtomicCachedReferenceまたは何か(get()が呼び出され、現在の値== nullのときにオーバーライド可能なメソッドを呼び出す)を追加しなかった理由は私を超えています。
-
ehcache をお試しください。キャッシュ(つまり、キーがマップに含まれていない場合にコードを呼び出すことができるオブジェクト)を設定できます。これは通常あなたが望むものであり、キャッシュは本当にあなたの問題を解決します(そしてそれらが存在すら知らなかった他のすべての問題)。
他の人が指摘したように、このコードは意図したとおりに動作しますが、厳密でない一連の非自明な仮定の下でのみ:
- Javaコードはクラスター化されていません(@Greg Hの回答を参照)
- 「行」参照は、同期ブロックの前の最初の行でnullがチェックされる only です。