类图是:
WHY
正如定义所说,单例模式就是整个内存模型中,只有一个实例。实例少了,内存占用就少。同时,只有一个实例,也就只需要构建一个对象,计算就少。对于构造过程中需要大量计算或者占用大量资源的对象,只创建一次,就减少了资源占用和内存占用。
HOW
饿汉式
饿汉式是最简单的一种实现,在类装载过程中,完成实例化,避免多线程问题。
实现一:静态实例参数与静态代码块
public class EagerSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {
}
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
根据java的特性,饿汉式还可以变种写法,有的地方称为静态代码块方式:
public class EagerSingleton {
private static EagerSingleton INSTANCE = null;
static {
INSTANCE = new EagerSingleton();
}
private EagerSingleton() {
}
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
这两种方式只是在写法上的区别,优缺点没有区别,只是借助Java语言特性的不同写法,所以归为一类。
饿汉式有两个明显的缺点:
- 类装载过程即完成实例化,如果整个应用生命周期内,实例没有使用,也就是浪费资源了。
- 因为没有办法向构造函数传递不同的参数,如果需要通过个性化参数定制实例时,这种方式就不支持了。
实现二:静态内部类
针对饿汉式第一个缺点,我们可以借助静态内部类的方式,将对象实例化的时间延后。
public class EagerSingleton {
private EagerSingleton() {
}
private static class EagerSingletonInstance {
private static final EagerSingleton INSTANCE = new EagerSingleton();
}
public static EagerSingleton getInstance() {
return EagerSingletonInstance.INSTANCE;
}
}
但是,依然不能很好的解决第二个缺点,如果需要根据不同的参数实现不同的实例,可以采用下面说的懒汉式实现。
懒汉式
懒汉式比饿汉式的一个优点,就是能够在使用的时候再进行实例化。但是,馅饼总是要伴随着陷阱,懒汉式写法有更多的坑,一不小心就摔着了。
错误一:单线程实现
public class LazySingleton {
private static LazySingleton INSTANCE = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new LazySingleton();
}
return INSTANCE;
}
}
之所以定义为单线程实现,是因为INSTANCE == null
这个判断,一个线程通过这个判断,开始进行对象实例化,但是还没有实例化完成,另一个线程又来了,这个时候,对象还没有实例化,就也会开始进行实例化,造成不必要的浪费。
错误二:同步方法
public class LazySingleton {
private static LazySingleton INSTANCE = null;
private LazySingleton() {
}
public static synchronized LazySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new LazySingleton();
}
return INSTANCE;
}
}
这种方式解决了多线程的问题,但是也引入了新的性能问题:太慢。synchronized
把整个方法包起来,也就是每个线程进入的时候,都需要等待其他线程结束调用,才能拿到实例,在性能敏感的场景,是比较致命的。
错误三:同步代码块之单次检查
public class LazySingleton {
private static LazySingleton INSTANCE = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (INSTANCE == null) {
synchronized (LazySingleton.class) {
INSTANCE = new LazySingleton();
}
}
return INSTANCE;
}
}
这种写法看似将同步代码缩小,但也缩小了多线程保障,也犯了第一种写法的错误,属于没有对多线程有基本了解写出的低级错误代码。
错误四:同步代码块之双重检查
public class LazySingleton {
private static LazySingleton INSTANCE = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (INSTANCE == null) {
synchronized (LazySingleton.class) {
if (INSTANCE == null) {
INSTANCE = new LazySingleton();
}
}