经典单例模式
单例模式被定义为:确保一个类只有一个实例,并提供一个全局访问点。
单例模式使得一个类只能有唯一的一个实例,这意味着不能再使用new关键字创建对象,因为一旦可以被new,就可以被多次new。
问题1:如何保证一个类只有一个实例?
问题2:不能使用new如何获取对象?
这两个问题的答案非常简单:
- 由于new对象会调用构造方法,所以只需要将构造方法定义为private(虽然很少这么干,但单例模式必须如此);这样创建对象的任务只能在类中完成;
- 定义明确指出需要提供一个全局访问点,意思就是需要拿到类中定义的对象,可以使用static方法,以下是经典单例模式的实现
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if(singleton==null) {
singleton=new Singleton();
}
return singleton;
}
}
单例模式没有复杂的类图结构,而且代码非常简单,但仍然需要注意一些细节:
- 构造方法私有化,只有类中才可以调用构造方法
- 全局访问点被定义为static方法,直接通过类名就可以调用
- 在getInstance()中,if(singleton==null)和静态成员singleton配合组成了单例模式的核心,只有第一次调用getInstance()时,其中的代码会全部执行。singleton只会被初始化一次。
- 如果不需要Singleton的实例,永远不会创建它的实例,这称为延迟实例化(lazy instance)
单例模式的类图如下:
确实,目前为止单例模式非常简单;可是能够完全满足单例的需求吗?还需要看一看在多线程下的单例模式。
多线程下的单例模式
单例模式在多线程下可能会创建多个对象而违反单例模式的原则
如图所示,如下线程A进入if语句但还未创建对象是,因为某些原因导致A发生了阻塞,由于A还没有来得及创建对象,所以线程B又进入了if语句,创建了一个实例,B执行完成后,A继续执行,虽然此时以及singleton已经不为空,但是A依然会创建对象,此时就破坏了单例模式的原则。
为了保证在多线程环境下能够正确的使用单例模式,可以采用三种方法:
- 同步方法
- “急切”创建实例
- 双重加锁
同步方法
不需要做任何改变,只需要在getInstance()方法前假设synchronized关键字
public class Singleton {
private static Singleton singleton;
private Singleton() {}
* 同步方法实现多线程下的单例模式
* @return
*/
public static synchronized Singleton getInstance() {
if(singleton==null) {
singleton=new Singleton();
}
return singleton;
}
}
同步方法非常简单粗暴,带来的问题也很明显:降低性能,每一次调用全局访问点,都会是一种累赘。
‘急切’创建实例
急切创建实例的意思是在类加载静态singleton时就创建出实例,这样无论如何都不可能再有第二个实例产生,但这样就失去了延迟实例化的好处。
public class Singleton {
//急切创建实例
private static Singleton singleton=new Singleton();
private Singleton() {}
/**
* 急切创建实例实现多线程下的单例模式
*/
public static Singleton getInstance() {
return singleton;
}
}
这种方法在JVM加载类时即创建出实例,同时也简化了getInstance()方法
双重加锁
双重加锁也是采用同步的方法,但是会减少同步的使用以提高性能。
public class Singleton {
private volatile static Singleton singleton;
private Singleton() {}
/**
* 双重锁实现多线程下的单例模式
* @return
*/
public static Singleton getInstance() {
if(singleton==null) {
synchronized (Singleton.class) {
if(singleton==null) {
singleton=new Singleton();
}
}
}
return singleton;
}
}
同步代码块写在if中,意思是如果singleton没有实例化才进行同步;
volatile关键字能够保证一旦singleton被初始化后,多线程下可以正确的处理它。
总结
- 单例模式确保程序中一个类最多只能有一个实例
- 单例模式提高一个全局访问点获取唯一的实例
- 单例模式依赖于私有构造器,一个静态变量和一个静态方法
- 单例模式再多线程环境下可能失效,需要使用一定的改进方法
本文参考自《Head First 设计模式(中文版)》
作者:smartpig
微信公众号:SmartPig
个人博客:http://smartpig612.club