一部のシステムでは、メモリ リソースを節約し、データ コンテンツの一貫性を確保するために、特定のクラスのインスタンスを 1 つだけ作成する必要があります (いわゆるシングルトン モード)。
1. シングルトンパターンの定義と特徴
singleton() パターンの定義: クラスがインスタンスを 1 つだけ持ち、クラスがこのインスタンスを独自に作成できるパターンを指します。たとえば、Windows ではタスク マネージャーを 1 つだけ開くことができるため、複数のタスク マネージャー ウィンドウを開くことによるメモリ リソースの浪費や、各ウィンドウで表示内容が異なるなどのエラーを回避できます。
コンピュータ システムには、Windows のごみ箱、オペレーティング システムのファイル システム、マルチスレッドのスレッド プール、グラフィック カードのドライバ オブジェクト、プリンタのバックグラウンド処理サービス、プリンタのログ オブジェクトもあります。アプリケーション、データベースの接続プール、Web サイト カウンター、Web アプリケーションの構成オブジェクト、アプリケーションのダイアログ ボックス、システムのキャッシュなどは、多くの場合シングルトンとして設計されます。
シングルトン パターンには 3 つの特徴があります。
- シングルトン クラスにはインスタンス オブジェクトが 1 つだけあります。
- シングルトン オブジェクトは、シングルトン クラス自体によって作成される必要があります。
- シングルトン クラスは、シングルトンへのグローバル アクセス ポイントを提供します。
2. シングルトンパターンの構造と実装
シングルトン パターンは、最も単純なデザイン パターンの 1 つです。通常、通常のクラスのコンストラクタは public であり、外部クラスは「new construction()」を通じて複数のインスタンスを生成できます。ただし、クラスのコンストラクターをプライベートにすると、外部クラスはコンストラクターを呼び出すことができず、複数のインスタンスを生成できなくなります。このとき、クラス自体が静的プライベート インスタンスを定義し、静的プライベート インスタンスを作成または取得するための静的パブリック関数を提供する必要があります。
その基本構造と実装方法を分析してみましょう。
2.1. シングルトンパターンの構造
シングルトンパターンの主な役割は以下の通りです。
- シングルトンクラス: 1 つのインスタンスを含み、このインスタンスを単独で作成できるクラス。
- アクセスクラス:シングルトンを使用するクラス。
その構造を図 1 に示します。
2.2. シングルトンパターンの実装
通常、シングルトン パターンには 2 つの実装があります。
2.3.1. レイジーシングルトン
このモードの特徴は、クラスのロード時にシングルトンが生成されないことであり、このシングルトンは getInstance メソッドが初めて呼び出されたときにのみ作成されます。コードは以下のように表示されます。
public class LazySingleton
{
private static volatile LazySingleton instance=null; //保证 instance 在所有线程中同步
private LazySingleton(){} //private 避免类在外部被实例化
public static synchronized LazySingleton getInstance()
{
//getInstance 方法前加同步
if(instance==null)
{
instance=new LazySingleton();
}
return instance;
}
}
注: マルチスレッド プログラムを作成している場合は、上記のコード例でキーワード volatile および synchronized を削除しないでください。削除しないと、スレッドの非安全性の問題が発生します。これら 2 つのキーワードを削除しない場合、スレッドの安全性は確保できますが、アクセスのたびに同期が必要となり、パフォーマンスに影響を及ぼし、より多くのリソースを消費するため、遅延シングルトンの欠点となります。
2.3.2. ハングリーハンスタイルシングルトン
このモードの特徴は、クラスがロードされるとシングルトンが作成され、getInstance メソッドが呼び出される前にシングルトンがすでに存在していることが保証されることです。
public class HungrySingleton
{
private static final HungrySingleton instance=new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance()
{
return instance;
}
}
Hungry スタイルのシングルトンは、クラス作成時にシステム用の静的オブジェクトが既に作成されており、今後も変更されないため、スレッドセーフであり、マルチスレッドで直接使用しても問題ありません。
3. シングルトンモードを記述する 8 つの方法
3.1. ハングリーハンスタイルシングルトン
書き方1:
package com.company.singleton;
/**
* 饿汉式
* 类加载到内存后,就实例化一个单例,JVM保证线程安全
* 简单实用,推荐使用!
* 唯一缺点:不管用到与否,类装载时就完成实例化
* (话说你不用的,你装载它干啥)
*/
public class Mgr01 {
private static final Mgr01 INSTANCE = new Mgr01();
private Mgr01() {};
public static Mgr01 getInstance() {
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
Mgr01 m1 = Mgr01.getInstance();
Mgr01 m2 = Mgr01.getInstance();
System.out.println(m1 == m2);
}
}
書き方 2:
package com.company.singleton;
/**
* 跟01是一个意思
*/
public class Mgr02 {
private static final Mgr02 INSTANCE;
static {
INSTANCE = new Mgr02();
}
private Mgr02() {};
public static Mgr02 getInstance() {
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
Mgr02 m1 = Mgr02.getInstance();
Mgr02 m2 = Mgr02.getInstance();
System.out.println(m1 == m2);
}
}
3.2. レイジーシングルトン
書き方1;
package com.company.singleton;
/**
* lazy loading
* 也称懒汉式
* 虽然达到了按需初始化的目的,但却带来线程不安全的问题
*/
public class Mgr03 {
private static Mgr03 INSTANCE;
private Mgr03() {
}
public static Mgr03 getInstance() {
if (INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr03();
}
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->
System.out.println(Mgr03.getInstance().hashCode())
).start();
}
}
}
書き方その2;
package com.company.singleton;
/**
* lazy loading
* 也称懒汉式
* 虽然达到了按需初始化的目的,但却带来线程不安全的问题
* 可以通过synchronized解决,但也带来效率下降
*/
public class Mgr04 {
private static Mgr04 INSTANCE;
private Mgr04() {
}
public static synchronized Mgr04 getInstance() {
if (INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr04();
}
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Mgr04.getInstance().hashCode());
}).start();
}
}
}
書き方3:
package com.company.singleton;
/**
* lazy loading
* 也称懒汉式
* 虽然达到了按需初始化的目的,但却带来线程不安全的问题
* 可以通过synchronized解决,但也带来效率下降
*/
public class Mgr05 {
private static Mgr05 INSTANCE;
private Mgr05() {
}
public static Mgr05 getInstance() {
if (INSTANCE == null) {
//妄图通过减小同步代码块的方式提高效率,然后不可行
synchronized (Mgr05.class) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr05();
}
}
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Mgr05.getInstance().hashCode());
}).start();
}
}
}
書き方4:
package com.company.singleton;
/**
* lazy loading
* 也称懒汉式
* 虽然达到了按需初始化的目的,但却带来线程不安全的问题
* 可以通过synchronized解决,但也带来效率下降
*/
public class Mgr06 {
private static volatile Mgr06 INSTANCE; //JIT
private Mgr06() {
}
public static Mgr06 getInstance() {
if (INSTANCE == null) {
//双重检查
synchronized (Mgr06.class) {
if(INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr06();
}
}
}
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Mgr06.getInstance().hashCode());
}).start();
}
}
}
書き方5:
package com.company.singleton;
/**
* 静态内部类方式
* JVM保证单例
* 加载外部类时不会加载内部类,这样可以实现懒加载
*/
public class Mgr07 {
private Mgr07() {
}
private static class Mgr07Holder {
private final static Mgr07 INSTANCE = new Mgr07();
}
public static Mgr07 getInstance() {
return Mgr07Holder.INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
for(int i=0; i<1000; i++) {
new Thread(()->{
System.out.println(Mgr07.getInstance().hashCode());
}).start();
}
}
}
書き方6:
package com.company.singleton;
/**
* 不仅可以解决线程同步,还可以防止反序列化。
*/
public enum Mgr08 {
INSTANCE;
public void m() {}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Mgr08.INSTANCE.hashCode());
}).start();
}
}
}
4. シングルトンパターンの応用例
[例 1] 遅延シングルトン パターンを使用して、現在の米国大統領オブジェクトの生成をシミュレートします。
分析: 各任期中、米国の大統領は 1 人だけであるため、この例はシングルトン モデルを使用した実装に適しています。図 2 は、遅延スタイルのシングルトンを使用して実装された構造図を示しています。
図2 米国大統領発電機の構造図
プログラムコードは次のとおりです。
public class SingletonLazy
{
public static void main(String[] args)
{
President zt1=President.getInstance();
zt1.getName(); //输出总统的名字
President zt2=President.getInstance();
zt2.getName(); //输出总统的名字
if(zt1==zt2)
{
System.out.println("他们是同一人!");
}
else
{
System.out.println("他们不是同一人!");
}
}
}
class President
{
private static volatile President instance=null; //保证instance在所有线程中同步
//private避免类在外部被实例化
private President()
{
System.out.println("产生一个总统!");
}
public static synchronized President getInstance()
{
//在getInstance方法上加同步
if(instance==null)
{
instance=new President();
}
else
{
System.out.println("已经有一个总统,不能产生新总统!");
}
return instance;
}
public void getName()
{
System.out.println("我是美国总统:特朗普。");
}
}
プログラムを実行した結果は次のようになります。
社長をプロデュース! 私は米国大統領、トランプです。 すでに社長がいるのに、新しい社長は生まれない! 私は米国大統領、トランプです。 彼らは同一人物です!
[例 2] Hungry スタイルのシングルトン モードを使用して、Zhubajie オブジェクトの生成をシミュレートします。
分析: 上記の例と同様に、Zhu Bajie は 1 つだけであるため、この例もシングルトン モードでの実装に適しています。この例では、Zhu Bajie の画像を表示するため (プログラムで表示する Zhu Bajie の画像をダウンロードするにはここをクリックしてください)、フレーム形式の JFrame コンポーネントを使用します。ここでの Zhu Bajie クラスはシングルトン クラスです。これは、パネル JPanel のサブクラスとして定義できます。これには、Zhu Bajie の画像を保存するためのタグが含まれています。クライアント フォームは、Zhu Bajie オブジェクトを取得して表示できます。図 3 は、Hungry スタイルのシングルトンを使用して実装された構造図を示しています。
図3 Zhubajie発電機の構造図
プログラムコードは次のとおりです。
import java.awt.*;
import javax.swing.*;
public class SingletonEager
{
public static void main(String[] args)
{
JFrame jf=new JFrame("饿汉单例模式测试");
jf.setLayout(new GridLayout(1,2));
Container contentPane=jf.getContentPane();
Bajie obj1=Bajie.getInstance();
contentPane.add(obj1);
Bajie obj2=Bajie.getInstance();
contentPane.add(obj2);
if(obj1==obj2)
{
System.out.println("他们是同一人!");
}
else
{
System.out.println("他们不是同一人!");
}
jf.pack();
jf.setVisible(true);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
class Bajie extends JPanel
{
private static Bajie instance=new Bajie();
private Bajie()
{
JLabel l1=new JLabel(new ImageIcon("src/Bajie.jpg"));
this.add(l1);
}
public static Bajie getInstance()
{
return instance;
}
}
プログラムの実行結果を図 4 に示します。
図4 Zhubajieジェネレータの実行結果
5. シングルトンモードの適用シナリオ
シングルトンパターンの構造と特徴は以前から解析されており、一般的に適用されるシナリオの特徴は以下のとおりです。
- アプリケーション シナリオでは、クラスのモニターや各人の ID 番号など、特定のタイプで 1 つのオブジェクトの生成のみが必要な場合。
- オブジェクトを共有する必要がある場合。シングルトン パターンでは作成できるオブジェクトは 1 つだけであるため、オブジェクトを共有するとメモリが節約され、オブジェクト アクセスが高速化されます。Web 内の構成オブジェクト、データベース接続プールなど。
- マルチスレッドのスレッド プール、ネットワーク接続プールなど、特定のクラスを頻繁にインスタンス化する必要があり、作成されたオブジェクトが頻繁に破棄される場合。
6. シングルトンパターンの拡張
シングルトン モードは、制限付きマルチインスタンス (Multitcm) モードに拡張できます。このモードでは、限られた数のインスタンスを生成し、それらを ArrayList に保存できます。これは、必要に応じて顧客がランダムに取得できます。構造図を図に示します。 5.
図5 限定マルチインスタンスモードの構成図
参考記事: シングルトンパターン(シングルケース設計パターン)の詳細解説_smile_and_ovoのブログ - CSDNブログ_シングルケースパターン