单例模式 (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
推荐阅读:
博文:赵学智@行胜于言—— 设计模式培训之一:为什么要用单例模式?
博文:都市耕牛—— 登记式单例实现单例模式的继承(限定一个抽象类的所有子类都必须是单例)