マルチスレッドを考慮したSingleton
例えば、以下のようなJavaコードがあったとします。
public class Singleton { private static Singleton singleton = null; private Singleton() { System.out.println("インスタンスを生成しました"); } public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
このクラスはインスタンスが1個しか生成されなさそうですが、残念ながら複数生成されてしまう場合があります。
それは、マルチスレッド下でgetInstance()を呼んだときです。
つまり、複数のスレッドからほぼ同時にgetInstance()を呼んだとき、状態としてはsingletonはnullであるため、インスタンスは呼び出された分だけ生成されます。
わざとsleep処理をして同時に呼び出されるようにしてみると
public class Singleton { private static Singleton singleton = null; private Singleton() { System.out.println("インスタンスを生成しました"); slowdawn(); } public static synchronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } private void slowdawn() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
そして、呼び出してみます。
public class Main extends Thread { public Main(String name) { super(name); } public static void main(String[] args) { System.out.println("Start."); new Main("A").start(); new Main("B").start(); new Main("C").start(); System.out.println("End."); } @Override public void run() { Singleton obj = Singleton.getInstance(); System.out.println(getName() + ": obj = " + obj); } }
実行結果はこうなります。
Start. End. インスタンスを生成しました インスタンスを生成しました インスタンスを生成しました B: obj = Singleton@12d18a03 A: obj = Singleton@68fb341a C: obj = Singleton@68fb341a Process finished with exit code 0
AとCが同じインスタンスなのに対して、Bは違うインスタンスであることがわかります。
そこで、getInstance()にsyncronized をつけてあげることで、ほかのスレッドからブロックします。 こうしてあげることで、スレッドセーフになり、Singletonが成り立ちます。
public class Singleton { private static Singleton singleton = null; private Singleton() { System.out.println("インスタンスを生成しました"); } public static syncronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
実行すると、
Start. End. インスタンスを生成しました A: obj = Singleton@68fb341a C: obj = Singleton@68fb341a B: obj = Singleton@68fb341a Process finished with exit code 0
すべて同じインスタンスとなりました。