
Singleton を Java で
Singleton は、 生成に関するデザインパターンの一つで、 この種類のオブジェクトがただ一つだけ存在することを保証し、 他のコードに対して唯一のアクセス・ポイントを提供します。
Singleton には、 大域変数とほぼ同じ長所と短所があります。 両方とも随分と便利ですが、 コードのモジュール性を犠牲にしています。
シングルトンのクラスに依存しているあるクラスを使う場合、 シングルトンのクラスも一緒に使う必要があります。 ほとんどの場合、 この制限は、 ユニット・テストの作成で問題となります。
複雑度:
人気度:
使用例: 多くの開発者は、 Singleton をアンチ・パターンと見なしています。 Java コードでの使用が減少したのはこのためです。
にもかかわらず、 Java のコア・ライブラリーには、 Singleton 利用の例が結構たくさんあります:
見つけ方: Singleton は、 キャッシュされた同一オブジェクトを返す静的生成メソッドで識別できます。
素朴なシングルトン(シングル・スレッド)
いい加減なシングルトンの実装は、 超簡単です。 コンストラクターを隠して、 静的生成メソッドを一つ書くだけです。
Singleton.java: シングルトン
package refactoring_guru.singleton.example.non_thread_safe; public final class Singleton { private static Singleton instance; public String value; private Singleton(String value) { // The following code emulates slow initialization. try { Thread.sleep(1000); } catch (InterruptedException ex) { ex.printStackTrace(); } this.value = value; } public static Singleton getInstance(String value) { if (instance == null) { instance = new Singleton(value); } return instance; } }
DemoSingleThread.java: クライアント・コード
package refactoring_guru.singleton.example.non_thread_safe; public class DemoSingleThread { public static void main(String[] args) { System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" + "If you see different values, then 2 singletons were created (booo!!)" + "\n\n" + "RESULT:" + "\n"); Singleton singleton = Singleton.getInstance("FOO"); Singleton anotherSingleton = Singleton.getInstance("BAR"); System.out.println(singleton.value); System.out.println(anotherSingleton.value); } }
OutputDemoSingleThread.txt: 実行結果
If you see the same value, then singleton was reused (yay!) If you see different values, then 2 singletons were created (booo!!) RESULT: FOO FOO
素朴なシングルトン(マルチ・スレッド)
同じクラスは、 マルチ・スレッド環境下では正しく動作しません。 複数のスレッドが、 生成メソッドを同時に呼ぶことができ、 シングルトン・クラスの複数のインスタンスができてしまいます。
Singleton.java: シングルトン
package refactoring_guru.singleton.example.non_thread_safe; public final class Singleton { private static Singleton instance; public String value; private Singleton(String value) { // The following code emulates slow initialization. try { Thread.sleep(1000); } catch (InterruptedException ex) { ex.printStackTrace(); } this.value = value; } public static Singleton getInstance(String value) { if (instance == null) { instance = new Singleton(value); } return instance; } }
DemoMultiThread.java: クライアント・コード
package refactoring_guru.singleton.example.non_thread_safe; public class DemoMultiThread { public static void main(String[] args) { System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" + "If you see different values, then 2 singletons were created (booo!!)" + "\n\n" + "RESULT:" + "\n"); Thread threadFoo = new Thread(new ThreadFoo()); Thread threadBar = new Thread(new ThreadBar()); threadFoo.start(); threadBar.start(); } static class ThreadFoo implements Runnable { @Override public void run() { Singleton singleton = Singleton.getInstance("FOO"); System.out.println(singleton.value); } } static class ThreadBar implements Runnable { @Override public void run() { Singleton singleton = Singleton.getInstance("BAR"); System.out.println(singleton.value); } } }
OutputDemoMultiThread.txt: 実行結果
If you see the same value, then singleton was reused (yay!) If you see different values, then 2 singletons were created (booo!!) RESULT: FOO BAR
遅延ロード付き、スレッド・セーフなシングルトン
問題を可決するには、 最初のシングルトン・オブジェクトの生成の間、 スレッドを同期する必要があります。
Singleton.java: シングルトン
package refactoring_guru.singleton.example.thread_safe; public final class Singleton { // The field must be declared volatile so that double check lock would work // correctly. private static volatile Singleton instance; public String value; private Singleton(String value) { this.value = value; } public static Singleton getInstance(String value) { // The approach taken here is called double-checked locking (DCL). It // exists to prevent race condition between multiple threads that may // attempt to get singleton instance at the same time, creating separate // instances as a result. // // It may seem that having the `result` variable here is completely // pointless. There is, however, a very important caveat when // implementing double-checked locking in Java, which is solved by // introducing this local variable. // // You can read more info DCL issues in Java here: // https://refactoring.guru/java-dcl-issue Singleton result = instance; if (result != null) { return result; } synchronized(Singleton.class) { if (instance == null) { instance = new Singleton(value); } return instance; } } }
DemoMultiThread.java: クライアント・コード
package refactoring_guru.singleton.example.thread_safe; public class DemoMultiThread { public static void main(String[] args) { System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" + "If you see different values, then 2 singletons were created (booo!!)" + "\n\n" + "RESULT:" + "\n"); Thread threadFoo = new Thread(new ThreadFoo()); Thread threadBar = new Thread(new ThreadBar()); threadFoo.start(); threadBar.start(); } static class ThreadFoo implements Runnable { @Override public void run() { Singleton singleton = Singleton.getInstance("FOO"); System.out.println(singleton.value); } } static class ThreadBar implements Runnable { @Override public void run() { Singleton singleton = Singleton.getInstance("BAR"); System.out.println(singleton.value); } } }
OutputDemoMultiThread.txt: 実行結果
If you see the same value, then singleton was reused (yay!) If you see different values, then 2 singletons were created (booo!!) RESULT: BAR BAR
もっと知りたいですか?
Java には、 もっと特殊な Singleton パターンの変わり種があります。 詳しくは、 下記の記事をご覧ください:
Java Singleton Design Pattern Best Practices with Examples (例題付き、 Java の Singleton デザインパターンのベスト・プラクティス)