web-dev-qa-db-ja.com

Javaダブルチェックロック

私は最近Javaでダブルチェックされたロックパターンとその落とし穴について議論している記事に出くわしました、そして今私は今私が何年も使用しているそのパターンの変形が影響を受けるかどうか疑問に思っています問題。

私はこの件に関する多くの投稿と記事を見て、部分的に構築されたオブジェクトへの参照を取得することで潜在的な問題を理解しました、そして私の知る限り、私はしませんthink私の実装はこれらの問題に応じて。次のパターンに問題はありますか?

そして、そうでなければ、なぜ人々はそれを使わないのですか?この問題に関して私が見たどのディスカッションでも、これが推奨されているのを見たことがありません。

public class Test {
    private static Test instance;
    private static boolean initialized = false;

    public static Test getInstance() {
        if (!initialized) {
            synchronized (Test.class) {
                if (!initialized) {
                    instance = new Test();
                    initialized = true;
                }
            }
        }
        return instance;
    }
}
39
Jim

ダブルチェックロックが壊れています 。初期化はプリミティブであるため、動作するために揮発性である必要はありませんが、インスタンスが初期化される前に、非同期化されたコードに対して初期化が正しいと見なされることを妨げるものはありません。

編集:上記の答えを明確にするために、元の質問はブール値を使用してダブルチェックロックを制御することについて尋ねました。上記のリンクのソリューションがないと、機能しません。実際にブール値を設定してロックを再確認することもできますが、クラスインスタンスの作成に関しては、命令の並べ替えに問題があります。非同期のブロックで初期化されたブール値がtrueと表示された後、インスタンスが初期化されない可能性があるため、提案されたソリューションは機能しません。

ロックをダブルチェックする適切な解決策は、(インスタンスフィールドで)揮発性を使用し、初期化されたブール値を忘れ、JDK 1.5以上を使用するか、またはリンクされているように最終フィールドで初期化することです記事とトムの答え、またはそれを使用しないでください。

確かに、このシングルトンを取得する際に大量のスレッドの競合が発生することがわかっている場合、またはアプリケーションのプロファイルを作成してこれがホットスポットであることがわかっていない限り、概念全体は非常に時期尚早の最適化のように見えます。

25
Yishai

initializedvolatileだった場合、これは機能します。 synchronizedの場合と同様に、volatileの興味深い効果は、他のデータについて言えることほど、参照とあまり関係がありません。 instanceフィールドとTestオブジェクトの設定は、happen-beforeinitializedへの書き込みを強制されます。キャッシュされた値を短絡経由で使用する場合、initialize読み取りhappens-beforeinstanceの読み取りと参照を通じて到達したオブジェクト。個別のinitializedフラグを使用しても、大きな違いはありません(コードがさらに複雑になる以外は)。

(安全でない公開のためのコンストラクターのfinalフィールドの規則は少し異なります。)

ただし、この場合、バグが表示されることはほとんどありません。初めて使用する際にトラブルが発生する可能性は少なく、繰り返しのないレースです。

コードは複雑すぎます。あなたはそれを次のように書くことができます:

private static final Test instance = new Test();

public static Test getInstance() {
    return instance;
}
16

ダブルチェックロックは確かに壊れており、問題の解決策は実際にはこのイディオムよりもコード的に実装する方が簡単です-静的初期化子を使用するだけです。

public class Test {
    private static final Test instance = createInstance();

    private static Test createInstance() {
        // construction logic goes here...
        return new Test();
    }

    public static Test getInstance() {
        return instance;
    }
}

静的初期化子は、JVMがクラスを初めてロードしたとき、およびクラス参照を任意のスレッドに返す前に実行されることが保証されており、本質的にスレッドセーフになります。

12
matt b

これが、ダブルチェックロックが解除される理由です。

同期は、1つのスレッドのみがコードのブロックに入ることができることを保証します。ただし、同期セクション内で行われた変数の変更が他のスレッドから見えることは保証されません。同期されたブロックに入るスレッドのみが、変更を確認できます。これが、ダブルチェックロックが無効になる理由です。リーダー側で同期されません。読み取りスレッドは、シングルトンがnullではないことを確認できますが、シングルトンデータが完全に初期化されていない(表示されていない)可能性があります。

注文はvolatileによって提供されます。 volatileは順序付けを保証します。たとえば、揮発性シングルトンスタティックフィールドへの書き込みは、揮発性スタティックフィールドへの書き込みの前にシングルトンオブジェクトへの書き込みが完了することを保証します。 2つのオブジェクトのシングルトンの作成を妨げるものではありません。これは、同期によって提供されます。

クラスの最終的な静的フィールドは、揮発性である必要はありません。 Javaでは、 [〜#〜] jvm [〜#〜] がこの問題を処理します。

私の投稿を参照してください への回答シングルトンパターンと実際の世界での壊れたダブルチェックロックJava application 、ダブルチェックされたロックに関するシングルトンの例を示しています。

5

ダブルチェックロックはアンチパターンです。

レイジー初期化ホルダークラスは、注目すべきパターンです。

他にも非常に多くの回答があるにもかかわらず、DCLが多くの状況で壊れている理由、DCLが不要である理由、および代わりに何をすべきかを示す簡単な答えはまだないので、私は答えるべきだと考えました。したがって、私はGoetz: Java Concurrency In Practiceからの引用を使用します。これは、Javaメモリモデルに関する最後の章で最も簡潔な説明を提供します。

変数の安全な公開についてです。

DCLの実際の問題は、同期せずに共有オブジェクト参照を読み取るときに発生する可能性のある最悪の事態は、古い値(この場合はnull)を誤って参照することであるという仮定です。その場合、DCLイディオムは、ロックを保持したまま再試行することにより、このリスクを補正します。しかし、最悪のケースは実際にはかなり悪化します。参照の現在の値を確認することは可能ですが、オブジェクトの状態の値が古くなり、オブジェクトが無効または不適切な状態にあると見なされる可能性があります。

JMM(Java 5.0以降)のその後の変更により、リソースがvolatileにされた場合にDCLが機能するようになりました。揮発性の読み取りは通常、不揮発性の読み取りよりもわずかに高価であるため、パフォーマンスへの影響はわずかです。

ただし、これはユーティリティの大部分が成功したイディオムです。これを動機づけた力(低速の無競合の同期、低速のJVM起動)は機能していないため、最適化としての効果は低くなります。遅延初期化ホルダーのイディオムは同じ利点を提供し、理解しやすくなります。

リスト16.6。遅延初期化ホルダークラスイディオム

public class ResourceFactory
    private static class ResourceHolder {
        public static Resource resource = new Resource();
    }

    public static Resource getResource() {
        return ResourceHolder.resource;
    }
}

それがその方法です。

0
Adam

ダブルチェックが使用される場合もあります。

  1. まず、本当にシングルトンが必要ない場合、多くのオブジェクトを作成および初期化しないために、ダブルチェックが使用されます。
  2. コンストラクター/初期化済みブロックの最後にfinalフィールドセットがあります(これにより、以前に初期化されたすべてのフィールドが他のスレッドに表示されます)。
0
user590444

DCLの問題は、多くのVMで機能しているように見えても壊れています。ここに問題についての素晴らしい記事があります http://www.javaworld.com/article/2075306/Java-concurrency/can-double-checked-locking-be-fixed-.html

マルチスレッドとメモリの一貫性は、見た目よりも複雑なテーマです。 [...] Javaがまさにこの目的のために提供するツール-同期です。別のスレッドによって書き込まれた、または読み取られた可能性のある変数へのアクセスごとに、メモリの一貫性の問題は発生しません。

この問題を適切に解決する唯一の方法は、遅延初期化を回避する(熱心にそれを行う)か、同期ブロック内でシングルチェックを行うことです。ブールinitializedの使用は、参照自体のnullチェックと同等です。 2番目のスレッドはinitializedがtrueであることを確認できますが、instanceがnullまたは部分的に初期化されている可能性があります。

0
Adam

まず、この質問で説明されているように、シングルトンの場合はEnumを使用できます JavaでのEnumを使用したシングルトンの実装

2番目に、Java 1.5なので、この記事の最後で説明するように、ダブルチェックロックで揮発性変数を使用できます。 https://www.cs.umd.edu /~pugh/Java/memoryModel/DoubleCheckedLocking.html

0
Victor Ionescu

私はダブルチェックのロックイディオムについて調査してきましたが、私が理解したところによると、Testクラスが不変でなければ、コードが部分的に構築されたインスタンスを読み取るという問題につながる可能性があります。

Javaメモリモデルは、不変オブジェクトを共有するための初期化の安全性の特別な保証を提供します。

同期を使用してオブジェクト参照を公開していなくても、安全にアクセスできます。

(非常に望ましい本からの引用Java並行性の実際)

したがって、その場合は、ダブルチェックのロックイディオムが機能します。

ただし、そうでない場合は、同期せずに変数インスタンスを返すため、インスタンス変数が完全に構築されない可能性があります(コンストラクターで提供された値ではなく、属性のデフォルト値が表示されます)。

Boolean変数は、Testクラスが初期化される前にtrueに設定される可能性があるため、問題を回避するために何も追加しません(同期されたキーワードは完全に並べ替えを回避せず、一部の文は順序を変更する場合があります)。 Javaメモリモデルには、これを保証するための事前発生ルールはありません。

また、32ビット変数はJavaでアトミックに作成されるため、ブール値を揮発性にしても何も追加されません。ダブルチェックのロックイディオムはそれらでも動作します。

Java 5なので、インスタンス変数を揮発性として宣言することで問題を修正できます。

ダブルチェックされたイディオムについての詳細は この非常に興味深い記事 で読むことができます。

最後に、私が読んだいくつかの推奨事項:

  • シングルトンパターンを使用する必要があるかどうかを検討します。多くの人がアンチパターンと見なしています。可能な場合は、依存性注入が推奨されます。チェック this

  • ほとんどの場合、それは努力する価値がないので、それを実装する前にダブルチェックされたロック最適化が本当に必要かどうか慎重に検討してください。また、静的フィールドでTestクラスを構築することを検討してください。遅延読み込みは、クラスの構築に多くのリソースが必要な場合にのみ役立ち、ほとんどの場合そうではありません。

それでもこの最適化を実行する必要がある場合は、これをチェックしてください link これは、試行しているものと同様の効果を達成するためのいくつかの代替手段を提供します。

0
Jorge

「初期化済み」がtrueの場合、「インスタンス」は完全に初期化する必要があります。1に1を加えたものが2と同じです。したがって、コードは正しいです。インスタンスは1回だけインスタンス化されますが、関数は100万回呼び出される可能性があるため、100万回マイナス1回の同期チェックを行わなくてもパフォーマンスは向上します。

0
jack

Java.util.concurrent.atomic では、おそらくアトミックデータ型を使用する必要があります。

0
Seun Osewa