web-dev-qa-db-ja.com

マルチスレッド環境でのシングルトンパターン

私のインタビュー中に、インタビュアーはシングルトンパターンで質問を始めました。以下に書きました。次に、彼はgetInstanceメソッド内のヌル性をチェックするべきではないか?

メンバーは静的型であり、同時に初期化されているため、[〜#〜] not [〜#〜]必要です。しかし、彼は私の答えに満足していなかったようです。私は正しいかどうか?

class Single {

        private final static Single sing = new Single();       
        private Single() {
        }        
        public static Single getInstance() {
            return sing;
        }
    }

次に、彼はマルチスレッド環境用のシングルトンクラスを作成するように依頼します。次に、ダブルチェックシングルトンクラスを作成しました。

  class MultithreadedSingle {        
        private static MultithreadedSingle single;       
        private MultithreadedSingle() {
        }        
        public static MultithreadedSingle getInstance() {
            if(single==null){
                    synchronized(MultithreadedSingle.class){
                      if(single==null){
                            single= new MultithreadedSingle(); 
                              }      
                      }
                   }
             return single;
        }
    }

それから、彼はsynchronizedを使用して再確認することに異議を唱え、それは役に立たないと言いました。なぜ2回チェックし、なぜ同期を使用しているのですか?私は彼に複数のシナリオを説得しようとしました。しかし、彼はしませんでした。

後で、自宅で、複数のスレッドを持つ単純なシングルトンクラスを使用している以下のコードを試しました。

public class Test {

    public static void main(String ar[]) {
        Test1 t = new Test1();
        Test1 t2 = new Test1();
        Test1 t3 = new Test1();
        Thread tt = new Thread(t);
        Thread tt2 = new Thread(t2);
        Thread tt3 = new Thread(t3);
        Thread tt4 = new Thread(t);
        Thread tt5 = new Thread(t);
        tt.start();
        tt2.start();
        tt3.start();
        tt4.start();
        tt5.start();

    }
}

final class Test1 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " : " + Single.getInstance().hashCode());
        }
    }

}
     class Single {

        private final static Single sing = new Single();       
        private Single() {
        }        
        public static Single getInstance() {
            return sing;
        }
    }

以下は出力です:

Thread-0 : 1153093538
Thread-0 : 1153093538
Thread-0 : 1153093538
Thread-0 : 1153093538
Thread-0 : 1153093538
Thread-4 : 1153093538
Thread-1 : 1153093538
Thread-2 : 1153093538
Thread-3 : 1153093538
Thread-3 : 1153093538
Thread-3 : 1153093538
Thread-3 : 1153093538
Thread-3 : 1153093538
Thread-2 : 1153093538
Thread-2 : 1153093538
Thread-2 : 1153093538
Thread-2 : 1153093538
Thread-1 : 1153093538
Thread-1 : 1153093538
Thread-1 : 1153093538
Thread-1 : 1153093538
Thread-4 : 1153093538
Thread-4 : 1153093538
Thread-4 : 1153093538
Thread-4 : 1153093538

それで、質問は、マルチスレッド環境でsynchronizeまたは/およびダブルチェックメソッドを使用する必要がありますか?私の最初のコード自体(余分なコード行を追加せずに)が両方の質問の答えだったようです。訂正と知識の共有をいただければ幸いです。

11
Ravi

あなたの最初の例は絶対に正しいです、そして通常シングルトンのための好ましい「イディオム」です。もう1つは、単一要素の列挙型を作成することです。

public enum Single {
    INSTANCE;

    ...
}

クラスがSerializableでない限り、2つのアプローチは非常に似ています。その場合、列挙型アプローチを正しく実行する方がはるかに簡単です。ただし、クラスがSerializableでない場合は、スタイル上の問題として、実際には列挙型アプローチをお勧めします。インターフェイスを実装したり、それ自体がシリアライズ可能であるクラスを拡張したりするために、「誤って」シリアライズ可能になることに注意してください。

また、ダブルチェックロックの例での2回目のヌルチェックについても正しいです。ただし、これがJavaで機能するには、singフィールド必須volatileである必要があります。それ以外の場合、singに書き込む1つのスレッドとそれを読み取る別のスレッドの間に、正式な「ハプニングビフォア」エッジはありません。これにより、最初のスレッドが変数に割り当てられていても、2番目のスレッドにnullが表示される可能性があります。または、singインスタンスに状態がある場合は、2番目のスレッドに一部のみが表示される可能性もあります。その状態(部分的に構築されたオブジェクトを参照)。

6
yshavit

1)クラス#1はマルチスレッド環境に適しています

2)クラス#2は、レイジー初期化とダブルチェックロックを備えたシングルトンです。これは既知のパターンであり、同期を使用する必要があります。しかし、あなたの実装は壊れています、それはフィールドにvolatileを必要とします。この記事で理由を確認できます http://www.javaworld.com/article/2074979/Java-concurrency/double-checked-locking--clever--but-broken.html

3)1つのメソッドを持つシングルトンは、クラスが最初の使用時にのみロードおよび初期化されるため、レイジーパターンを使用する必要はありません。

3

競合状態になる可能性はまったくないので、最初の答えは私にとって良いようです。

知識の共有に関しては、Javaでシングルトンを実装するための最良のアプローチは列挙型を使用することです。1つのインスタンスだけで列挙型を作成し、それだけです。コードサンプルについては-

public enum MyEnum {
    INSTANCE;

    // your other methods
}

良い本から 効果的なJava -

[....]このアプローチは、はるかに簡潔で、シリアル化機構を無料で提供し、高度なシリアル化またはリフレクションに直面しても、複数のインスタンス化に対する強力な保証を提供することを除いて、パブリックフィールドアプローチと機能的に同等です。攻撃。[...]単一要素の列挙型は、シングルトンを実装するための最良の方法です。

1
MD Sayem Ahmed

ケース#2の場合、静的フィールド「single」に「volatile」キーワードを追加します。

Double-Checked Locking を使用する場合は、このシナリオを考慮してください。

  1. スレッドAが最初に来て、ロックを取得し、オブジェクトの初期化に進みます。
  2. Javaメモリモデル(JMM)によると、メモリは変数に割り当てられ、Javaオブジェクトを初期化する前に公開されます。
  3. スレッドBが入り、ダブルチェックロックのため、変数が初期化されるため、ロックを取得しません。
  4. これは、オブジェクトが初期化されることを保証するものではなく、初期化されたとしても、CPUごとのキャッシュが更新されない場合があります。参照 キャッシュコヒーレンス

次に、volatileキーワードについて説明します。

揮発性変数は常にメインメモリに書き込まれます。したがって、キャッシュのインコヒーレンスはありません。

0
Kamaal

Double-checked_locking によると、おそらく最良の方法です

class Foo {
    private volatile Helper helper;
    public Helper getHelper() {
        Helper result = helper;
        if (result == null) {
            synchronized(this) {
                result = helper;
                if (result == null) {
                    helper = result = new Helper();
                }
            }
        }
        return result;
    }
}

または 初期化オンデマンドホルダーイディオム を使用する

public class Something {
    private Something() {}

    private static class LazyHolder {
        private static final Something INSTANCE = new Something();
    }

    public static Something getInstance() {
        return LazyHolder.INSTANCE;
    }
}
0
Nassim MOUALEK