定义
单例模式(Singleton Pattern)确保一个类只有一个实例,并提供一个全局访问点。——《HEAD First 设计模式》
主要角色
一个只能有一个实例的类:
Singleton类定义了一个getInstance操作,允许客户端访问它的唯一实例,getInstance是一个静态方法,主要负责创建和返回自己的唯一实例。而构造函数是private的,其他类无法访问到。
例子
源自《HEAD First 设计模式》的巧克力工厂。
巧克力工厂里面只有一个锅炉进行工作,不能创建其他锅炉实例。
public class ChocolateBoiler{
private static Singleton uniqueInstance;
private boolean empty;
private boolean boiled;
//构造函数声明为私有的,只有类内部才能访问
private ChocolateBoiler(){empty = true;boiled = false;}
//实例化对象并返回实例
public static ChocolateBoiler getInstance(){
if(uniqueInstance==null){
uniqueInstance=new ChocolateBoiler();
}
return uniqueInstance;
}
//其他方法
}
这样调用getInstance()可以获取到巧克力锅炉的实例,如果没有就在getInstance()里面进行创建。那么如果我们不去使用这给类,那么这个对象可能永远不会实例化,这种情况叫做延迟实例化,这种实例方式也叫懒汉式(Lazy Loading)。
但是这种情况下显然是无法实现多线程运行的。
多线程
上面介绍的懒汉式是只能用于单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
还有几种单例模式的实现方式:饿汉式、双重加锁机制、静态内部类
饿汉式
public class Singleton{
// 指向自己实例的私有静态引用,主动创建
private static Singleton singleton = new Singleton();
// 私有的构造方法
private Singleton(){}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton getSingleton(){
return singleton;
}
}
饿汉式单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。但是,如果不用这个实例,也会进行实例化,造成一定的浪费。
双重加锁机制
利用双重检查加锁(double-checked locking),首先检查是否实例已经创建了,如果未创建,才进行同步。
public class Singleton{
//volatile关键字确保:当uniqueInstance变量被初始化成Singleton实例时,多个线程正确的处理uniqueInstance变量
private volatile static Singleton uniqueInstance;
//其他有用实例化变量
private Singleton(){}
public static Singleton getInstance(){
//检查实例,如果不存在,就进入同步区域。只有第一次才彻底执行这些代码。
if(uniqueInstance==null){
synchronized (Singleton.class){
//再次检查,如果仍是null才创建实例
if(uniqueInstance==null){
uniqueInstance=new Singleton();
}
}
return uniqueInstance;
}
//其他方法
}
这样做既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。
静态内部类
静态内部类的方式效果类似双检锁,但实现更简单。只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式延迟加载。
public class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
枚举
上面实现单例的方式都需要额外的工作来实现序列化,而且可以使用反射强行调用私有构造器。采用枚举可以避免这个问题。
public enum Singleton {
instance;
public static void dosomething() {
}
}
使用时可以采用Singleton.instance
优缺点
优点
内存中只有一个实例,减少了内存开支,尤其一个对象需要频繁地创建销毁,而此时性能又无法优化时,单例模式的优势就非常明显。
避免对资源的多重占用(比如写文件操作,只有一个实例时,避免对同一个资源文件同时写操作),简单来说就是对唯一实例的受控访问。
在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。
缺点
没有接口,不能继承:实际上可以使用单例模式的机会并不是太多,构造函数是私有的导致它不能继承,如果改成public或者protected,继承之后会导致所有的派生类共享一个实例变量(因为instance是静态的)。
与单一职责冲突:一个类应该只做一件事情,但是单例模式在提供功能的同时还在负责管理自己的实例。
参考
简说设计模式——单例模式
深入理解设计模式(一):单例模式
单例模式的五种写法
JAVA设计模式之单例模式
HeadFirst 设计模式 5 单例模式(巧克力工厂)