8

私はシングルトンクラスを持っています:

public class Singleton {
    private static Singleton istance = null;

    private Singleton() {}

    public synchronized static Singleton getSingleton() {
        if (istance == null)
            istance = new Singleton();
        return istance;
    }

    public void work(){
            for(int i=0; i<10000; i++){
                Log.d("-----------", ""+i);
            }
    }
}

複数のスレッドが work() 関数を呼び出しています。

public class Main {

public static void main(String[] args) {

    new Thread (new Runnable(){
        public void run(){
            Singleton s = Singleton.getSingleton();
            s.work();}
    }).start();

    System.out.println("main thread");

    new Thread(new Runnable() { 
         public void run() {
             Singleton s = Singleton.getSingleton();
                s.work();
         }
    }).start();
}
}

2 つの作業関数が同時にインスタンス化されたかのように、2 つのスレッドが同時に実行されていることに気付きました。

同時に実行するのではなく、前のスレッドの代わりに最後のスレッドを実行したい。2番目の呼び出しで最初の呼び出しのメモリ空間をオーバーライドすることはJavaで可能ですか?

4

6 に答える 6

20

あなたのgetSingleton()メソッドはSINGLETON インスタンスを遅延初期化しようとしていますが、次の問題があります:

  • 変数へのアクセスはありませんsynchronized
  • 変数はそうではありませんvolatile
  • ダブルチェックロックを使用していません

そのため、競合状態 AMY により 2 つのインスタンスが作成されます。

同期せずにシングルトンを安全に遅延初期化するのが最善かつ最も簡単な方法は次のとおりです。

private static class Holder {
    static Singleton instance = new Singleton();
}

public static Singleton getSingleton() { // Note: "synchronized" not needed
    return Holder.instance;
}

これはスレッド セーフです。Java クラス ローダーの契約では、すべてのクラスが使用される前に静的初期化が完了しているためです。また、クラス・ローダーは、参照されるまでクラスをロードしません。2 つのスレッドがgetSingleton()同時に呼び出された場合Holderでも、クラスは 1 回だけロードされるため、1new Singleton()回だけ実行されます。

Holderクラスはメソッドからのみ参照されるため、これはまだ怠惰です。したがって、クラスは最初の呼び出しが行われたときにのみロードさ れます。getSingleton()HoldergetSingleton()

このコードはクラスローダーの内部同期に依存しているため、同期は必要ありません。これは防弾です。


このコード パターンは、シングルトンを使用する唯一の方法です。それは:

  • 最速 (同期なし)
  • 最も安全 (産業強度クラスのローダーの安全性に依存)
  • 最もクリーンなコード (最小限のコード - 二重にチェックされたロックは見苦しく、何をするにも多くの行があります)


もう 1 つの同様のコード パターン (同様に安全で高速) はenum、単一のインスタンスで を使用することですが、これは扱いにくく、意図があまり明確ではありません。

于 2012-10-14T00:41:33.773 に答える
5

@amit がコメントで述べたように、getSingleton()メソッドはsynchronized. これは、複数のスレッドが同時にインスタンスを要求する可能性があり、最初のスレッドがまだオブジェクトを初期化しており、次のスレッドがチェックするときに参照が null になる可能性があるためです。これにより、2 つのインスタンスが作成されます。

public static synchronized Singleton getSingleton() {
    if (istance == null)
        istance = new Singleton();
    return istance;
}

メソッドを としてマークすると、メソッドsynchronizedがブロックされ、一度に 1 つのスレッドのみが呼び出されます。これで問題が解決するはずです。

于 2012-10-13T23:45:44.147 に答える
1

synchronizedファクトリメソッドで使用する

public class Singleton {
    private static Singleton istance = null;

    private final Singleton() {} // avoid overrides

    public static synchronized Singleton getSingleton() {
        if (istance == null)
            istance = new Singleton();
        return istance;
    }

    public void work() { // not static, otherwise there's no need for the singleton
        // ...
    }
}

または、単純にプライベート最終初期化子を使用します (インスタンス化はクラスのロード時に行われます)

public class Singleton {
    private static final Singleton istance = new Singleton(); // class-load initialization

    private final Singleton() {} 

    public static Singleton getSingleton() { // no need for synchronized
        return istance;
    }

    public void work() { 
        // ...
    }
}
于 2012-10-13T23:53:11.827 に答える
0

私が必要としていたことをほぼ実行しているこのコードを思いつきました。元の質問は、「スレッドを使用せずに次のことを行うことは可能ですか?言語でメモリを直接操作することで可能ですか?」というものでした。答えが「いいえ」の場合は、次の点を改善するのを手伝ってくれるかもしれません:

public class Main {
private static Thread t;
public static void main(String[] args) {
    work();
    for (int i =0;i<100; i++);
    System.out.println("oooooooooooooooooooooooooooooooooooooooooo");
    for (int i =0;i<100; i++);
    work();
    for (int i =0;i<500; i++);
    System.out.println("oooooooooooooooooooooooooooooooooooooooooo");
}

public static void work(){
    if (t != null) t.interrupt();
    t= new Thread (new Runnable(){
            public void run(){
                // Pause for 4 seconds
                try {
                    Thread.sleep(600);
                } catch (InterruptedException e) {
                    // We've been interrupted: no more messages.
                    return;
                }
                for(int i=0; i<10000; i++){
                    System.out.println(i);
                }
            }
            });
    t.start();
}
}

このコードは、リスナーへの複数の呼び出しを「デバウンス」し、ユーザー入力でバーストを発生させるのに役立ちます。スリープ機能を使用する欠点があります。スリープ時間は、バースト内のイベントが時間のかかるタスクの実行を開始するのを防ぐのに十分な長さである必要があります (最後のイベントのみが必要です)。残念ながら、スリープ時間が長い場合でも、これが常に発生するという保証はありません。

于 2012-10-14T14:11:36.133 に答える
0

Resource holder given in Java Concurrency In Practice:http://www.javaconcurrencyinpractice.com/ is the best non-blocking singleton pattern available. The singleton is lazily initialized (both SingletonHolder and Singleton class is loaded at Run-Time when the getInstance() method is called the first time) and the access-or method is non-blocking.

public class SingletonFactory {

private static class SingletonHolder {
    static Singleton instance = new Singleton();
}

public static Singleton getInstance() {
    return SingletonFactory.SingletonHolder.instance;
}

static class Singleton{
}

}

于 2012-10-14T04:17:47.833 に答える