この記事では、23 のデザイン パターン シリーズのシングルトン パターンを引き続き紹介します。
概念:
シングルトン パターンは、Java の一般的な設計パターンです。シングルトン パターンを記述するには、主に 3 つのタイプがあります: 遅延スタイル シングルトン、および登録スタイル シングルトン。
シングルトンパターンには以下の特徴があります:
1. シングルトンクラスはインスタンスを 1 つだけ持つことができます。
2. シングルトンクラスは独自の一意のインスタンスを作成する必要があります。
3. シングルトンクラスは、このインスタンスを他のすべてのオブジェクトに提供する必要があります。
シングルトンパターンは、クラスがインスタンスを 1 つだけ持つことを保証し、クラス自体をインスタンス化し、このインスタンスをシステム全体に提供します。コンピュータ システムでは、スレッド プール、キャッシュ、ログ オブジェクト、ダイアログ ボックス、プリンタ、グラフィック カード ドライバ オブジェクトはシングルトンとして設計されることがよくあります。これらのアプリケーションはすべて、多かれ少なかれリソース マネージャーの機能を備えています。各コンピュータには複数のプリンタを搭載できますが、2 つの印刷ジョブが同時にプリンタに出力されることを防ぐために、存在できるプリンタ スプーラは 1 つだけです。各コンピュータは複数の通信ポートを持つことができ、システムはこれらの通信ポートを集中管理して、1 つの通信ポートが 2 つの要求によって同時に呼び出されないようにする必要があります。つまり、シングルトン モードを選択する目的は、矛盾した状態を回避し、長期的なポリシーを回避することです。
1. Lazy Singleton
//懒汉式单例类.在第一次调用的时候实例化自己 public class Singleton { private Singleton() {} private static Singleton single=null; //静态工厂方法 public static Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; } }
Singleton は、構築メソッドをプライベートに制限することで、クラスが外部からインスタンス化されるのを回避します。同じ仮想マシンのスコープ内では、Singleton の唯一のインスタンスには getInstance() メソッドを通じてのみアクセスできます。 。
(実際、Java リフレクション メカニズムを通じて、プライベート コンストラクターを使用してクラスをインスタンス化することができます。これにより、基本的にすべての Java シングルトン実装が無効になります。この問題についてはここでは説明しません。リフレクション メカニズムは無視しましょう。 )
しかし、上記の遅延スタイルのシングルトン実装では、スレッド安全性の問題が考慮されていません。スレッド安全性を実現するには、次の 3 つの方法があります。遅延スタイル シングルトンのスレッド セーフを確保するための getInstance メソッドの変更。シングルトン モデルを初めて使用し、スレッド セーフについてよく知らない場合は、次の 3 つの小さな項目をスキップして、遅延スタイルに進んでください。たとえば、読み終わるまで待ってから、スレッドの安全性の問題を検討してください:
1. getInstance メソッドに同期を追加します
public static synchronized Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; }
2. 静的内部クラスを再確認します
3.public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }
これは上記の 1 と 2 よりも優れており、スレッドの安全性を実現するだけでなく、同期によるパフォーマンスへの影響も回避できます。
2. Hungry スタイルのシングルトン
public class Singleton { private static class LazyHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return LazyHolder.INSTANCE; } }
Hungry-Han スタイルは、クラスの作成時にシステムで使用する静的オブジェクトをすでに作成しており、将来的には変更しないため、本質的にスレッドセーフです。
3. 登録されたシングルトン (無視できます)
//饿汉式单例类.在类初始化时,已经自行实例化 public class Singleton1 { private Singleton1() {} private static final Singleton1 single = new Singleton1(); //静态工厂方法 public static Singleton1 getInstance() { return single; } }
登録されたシングルトンは、実際にはシングルトン クラスのインスタンスのセットを維持し、これらのインスタンスを Map (登録ブック) に保存します。登録されている場合は、マップから直接返されます。登録されていない場合は、最初に登録されてから返されます。
ここでは、登録スタイルのシングルトンを無視できるものとしてマークしました。さらに、静的メソッド ブロックとそのシングルトンのため、内部実装では依然として中国スタイルのシングルトンが使用されています。クラスがロードされるときにインスタンス化されます。
ハングリーマンスタイルとレイジーマンスタイルの違い
ハングリーマンとレイジーマンという名前から、
ハングリーマンは、クラスがロードされると、getInstance時に確実にシングルトンが初期化されることを意味します。シングルトンはすでに存在します はい、
怠け者は怠け者で、getInstance が呼び出されたときにのみシングルトンの初期化に戻ります。
さらに、次の 2 つのメソッドは次の 2 つの点で区別されます:
1. スレッド セーフ:
Hungry Chinese スタイルは本質的にスレッド セーフであり、問題なくマルチスレッドに直接使用できます。
遅延スタイル自体はスレッド セーフではありません。スレッド セーフを実現するにはいくつかの方法があります。つまり、上記 1、2、および 3 には、リソースの読み込みとパフォーマンスにいくつかの違いがあります。
2. リソースの読み込みとパフォーマンス:
什么是线程安全?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。
应用
以下是一个单例类使用的例子,以懒汉式为例,这里为了保证线程安全,使用了双重检查锁定的方式:
public class TestSingleton { String name = null; private TestSingleton() { } private static volatile TestSingleton instance = null; public static TestSingleton getInstance() { if (instance == null) { synchronized (TestSingleton.class) { if (instance == null) { instance = new TestSingleton(); } } } return instance; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void printInfo() { System.out.println("the name is " + name); } }
可以看到里面加了volatile关键字来声明单例对象,既然synchronized已经起到了多线程下原子性、有序性、可见性的作用,为什么还要加volatile呢,原因已经在下面评论中提到,
还有疑问可参考http://www.iteye.com/topic/652440
和http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
public class TMain { public static void main(String[] args){ TestStream ts1 = TestSingleton.getInstance(); ts1.setName("jason"); TestStream ts2 = TestSingleton.getInstance(); ts2.setName("0539"); ts1.printInfo(); ts2.printInfo(); if(ts1 == ts2){ System.out.println("创建的是同一个实例"); }else{ System.out.println("创建的不是同一个实例"); } } }
运行结果:
结论:由结果可以得知单例模式为一个面向对象的应用程序提供了对象惟一的访问点,不管它实现何种功能,整个应用程序都会同享一个实例对象。
对于单例模式的几种实现方式,知道饿汉式和懒汉式的区别,线程安全,资源加载的时机,还有懒汉式为了实现线程安全的3种方式的细微差别。