单例模式 (Singleton pattern)

What is Singleton pattern?

In Wikipedia, there is an explanation:"In software engineering, the singleton pattern is a design pattern that restricts the instantiation of a class to one object."

一、什么是单例模式?

在维基百科中,是这样解释的,“在软件工程中,单例模式指的是对类加以限制,只允许创建一个对象的设计模式”。

也就是说,在整个应用程序的生命周期中,任何一个时刻,单例类的实例都只存在一个(当然也可以不存在)。

单例模式是一种对象创建型设计模式,在《设计模式:可复用面向对象软件的基础》一书中提到,单例模式:“保证一个类仅有一个实例,并提供一个访问它的全局访问点”。

让类自身负责保存它的唯一实例,这个类可以保证没有其他实例被创建(通过截取创建新对象的请求)。

二、我们为什么要使用单例模式?

单例模式应该是设计模式中最简单的一种设计模式,它的应用场景如下:

在工作过程中,有些对象我们只需要一个,比如线程池、缓存、硬件设备等,

如果有多个实例同时使用,可能会造成执行冲突、结果不一致等问题。

比如我们创建了多个打印程序实例,或打印机对象,但实际的打印设备只有一台,或者打印假脱机只有一个,程序执行时,就可能会造成打印结果的混乱并使程序失去可再现性。

再比如,在一个父容器中点击某个菜单项打开一个子窗口,如果不加以控制的话,每次单击菜单项都会打开一个新窗口。这不仅会浪费内存资源,在程序逻辑上也是不可以接受的。

那么我们该如何解决这个问题呢?这就要用到下面所要详细介绍的单例模式。

三、经典单例模式

1.经典单例模式的UML图如下:

2.代码实现如下:

 1 public class Singleton {
 2 
 3     // 静态的instance对象,保证全局唯一性
 4     private static Singleton instance = null;
 5 
 6     // 私有的构造函数,防止外部用new关键字创建实例对象
 7     private Singleton() {
 8 
 9     }
10 
11     // 对外的公共静态实例方法,从类级别直接可以调用此方法
12     public static Singleton getInstance() {
13 
14         // 通过判断instance是否为null,决定是否创建对象
15         if (instance == null) {
16             instance = new Singleton();
17         }
18         return instance;
19     }
20 }

 注意:通过Java反射机制是能够实例化构造方法为private的类的,此时基本上所有的Java单例实现失效。

(事实上我们一般不需要这样做,所以单例模式仍有其存在的意义)

四、单例模式在多线程环境下存在的问题

对于上述代码,我们可以考虑这样一种情况:

当有两个Singleton类的实例同时被创建,并运行于不同的线程中。假如A线程在执行完上述代码第15行后意外阻塞,而B线程将继续运行,执行第16行后创建instance实例,此后如果A线程重新回到就绪状态,并得到处理器资源,进入运行状态,将再次创建instance对象,此时单例模式失效。为了解决这一问题,程序员们对单例模式进行了优化。

五、单例模式的分类

1.饿汉式单例

急切创建实例,在类初始化时,便自行实例化。

 1 public class Singleton1 {
 2 
 3     //已经实例化
 4     private static final Singleton1 single = new Singleton1();
 5 
 6     //私有的默认构造方法
 7     private Singleton1() {
 8     
 9     }
10     
11     //静态工厂方法
12     public static Singleton1 getInstance() {
13         return single;
14     }
15 }

2.懒汉式单例

在第一次调用的时候再实例化。

 1 public class Singleton2 {
 2 
 3     //注意,这里没有final
 4     private static Singleton2 single = null;
 5     
 6     //私有的默认构造子
 7     private Singleton2() {
 8     
 9     }
10     
11     //静态工厂方法,用synchronized加锁
12     public synchronized static Singleton2 getInstance() {
13          if (single == null) {
14              single = new Singleton2();
15          }
16         return single;
17     }
18 }

这种方式也存在一定的问题,同样考虑特殊情况,当A线程执行到getInstance内时意外出现阻塞,此时B线程中的实例也不能够执行getInstance操作,出现阻塞。

为了解决这一问题,对此方法可以做进一步优化:双重检查加锁法。

虽然此方法同样不完美,但相对于上面一种情形,已经做到了一定的优化。

 1 public class Singleton3 {
 2     
 3     // 添加关键词volatile
 4     private volatile static Singleton3 single = null;
 5     
 6     //私有的默认构造器
 7     private Singleton3() {
 8         
 9     }
10     
11     public static Singleton3 getInstance() {
12          if (single == null) {
13              //同步锁
14             synchronized (Singleton3.class) {
15                 if (single == null) {
16                     single = new Singleton3();
17                 }
18             }
19         }
20         return single;
21     }
22 }

3.登记式单例

将类名注册,下次从里面直接获取。

 1 import java.util.HashMap;
 2 import java.util.Map;
 3 
 4 public class Singleton4 {
 5     private static Map<String,Singleton4> map = new HashMap<String,Singleton4>();
 6     
 7     static{
 8         Singleton4 single = new Singleton4();
 9         map.put(single.getClass().getName(), single);
10     }
11     //保护的默认构造器
12     protected Singleton4(){
13     
14     }
15     
16     //静态工厂方法,返还此类惟一的实例
17     public static Singleton4 getInstance(String name) {
18         if(name == null) {
19             name = Singleton3.class.getName();
20         }
21         if(map.get(name) == null) {
22             try {
23                 map.put(name, (Singleton4) Class.forName(name).newInstance());
24             } catch (InstantiationException e) {
25                 e.printStackTrace();
26             } catch (IllegalAccessException e) {
27                 e.printStackTrace();
28             } catch (ClassNotFoundException e) {
29                 e.printStackTrace();
30             }
31         }
32         return map.get(name);
33     }
34 }

参考资料:

1.极客学院hexter老师的课程—— 设计模式之单例模式

2.《设计模式:可复用面向对象软件的基础》

  Erich Gamma,Richard Helm,Ralph Johnson著

3.博文:蛊惑Into—— Java单例模式详解

4.维基百科:Singleton Pattern

推荐阅读:

博文:赵学智@行胜于言——  设计模式培训之一:为什么要用单例模式? 

博文:都市耕牛—— 登记式单例实现单例模式的继承(限定一个抽象类的所有子类都必须是单例)

原文地址:https://www.cnblogs.com/yanqiang/p/4875684.html