单例模式
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点
动机:对一些类来说,只有一个实例是很重要的。让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建(通过截取创建新对象的请求),并且它可以提供一个访问该实例的方法。
适用性
- 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
- 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
结构
参与者
- Singleton
- 定义一个Instance操作,允许客户访问它的唯一实例。Instance是一个类操作(即一个静态成员函数)。
- 可能负责创建它自己的唯一实例
协作
客户只能通过Singleton的Instance操作访问一个Singleton的实例
效果
- 对唯一实例的受控访问
- 缩小名空间,Singleton模式是对全局变量的一种改进。
- 允许对操作和表示的精化。Singleton类可以有子类,而且用这个扩展类的实例来配置一个应用是很容易的
- 允许可变数目的实例
- 比类(静态类)操作更灵活(静态类必须是无状态的,Singleton可以是有状态的)
实现
Eageer Singleton:私有化构造函数,定义一个static成语并在声明时候初始化,并提供一个获取静态实例的静态方法。不会存在多线程环境下,实例创建的竞争问题,由JVM虚拟机初始化机制保障。
public final class Singleton() {} {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
public enum Singleton {
INSTANCE;
}
Lazy Singleton:用到的时候进行判断实例是否初始化,可能存在多线程并发的风险。
public class Singleton() {} {
private static Singleton instance;
private Singleton() {
}
public synchronized static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
//extension: double checked
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
双重检查DCL也不能保证多线程情况下只创建一个实例,由于JVM本身可能存在指令重排序等,导致创建多个实例(参考ifeve文章)。一般建议使用饿汉方式(Eager Singleton)。JDK5以后我们可以采用enum方式来创建单例(Effective Java中推荐的方式),和类懒加载方式性能相当,不过更简洁同样不会存在多线程竞争的问题。
Signleton的状态
Stateful object:有状态的对象包含并维护一个内部状态,状态可以通过方法调用和发生转换。一个类的不同有状态的对象是不一样的。
Stateless object:无状态的对象在方法调用的时候,不包含任何状态,一个类的不同无状态的对象是相同的。
Signleton可以是有状态的也可以是无状态的
- 有状态的Singleton所有clients共享这些内部状态信息,若多线程情况下访问这些状态信息方法,需要保证线程安全。
- 推荐使用无状态的Singleton
Singleton和继承
Singleton类声明如果是非final(或者构造函数声明为protected),这样其可以拥有子类。
- 若子类构造函数声明为public,这样Singleton就是不完整的
- 有些场景是又要的,但是不推荐去继承Singleton
JDK中Singleton实例
- Singleton (recognizeable by creational methods returning the same instance (usually of itself) everytime)
- java.lang.Runtime#getRuntime()
- java.awt.Desktop#getDesktop()
- java.awt.Toolkit#getDefaultToolkit()