单例模式是设计模式中最简单的一种创建型模式,使用场景一般有:工具类对象、系统中只能存在一个实例对象的类、创建频繁或又耗时耗资源且又经常用到的对象等。如:JDK的Runtime类就是饥饿的单例模式,以及Spring容器管理的实例Bean默认也是饥饿单例,在容器启动时初始化,当然也可以设置为懒汉式(default-lazy-init="true")。再如程序中引入公共线程池,为防止多次创建线程池浪费资源,公共线程池也可以采用单例模式实现的。
1、饥饿模式
类加载时默认初始化,实现最简单但也有一定的局限性,1、可能类加载时某些资源还未准备好,2、多个不同的自定义类加载器同时加载时可能导致重复初始化
public class HungrySingleton {
private static final HungrySingleton instance = new HungrySingleton ();
private HungrySingleton () {
// 一定要记得私有化构造器
}
public static HungrySingleton getInstance() {
return instance;
}
}
2、懒汉模式(要注意线程安全问题)
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton () { }
/**
* 一定要记得加synchronized
*/
public static synchronized LazySingleton getInstance() {
if(null == instance) {
instance = new LazySingleton ();
}
return instance;
}
}
3、双重检测锁模式
要注意线程安全隐患:多线程环境下获取到未初始化完全的实例对象,用volatile修饰对象可以防止该问题,详尽分析见下面注释
public class DoubleCheckSingleton {
/**
* TODO 这里主要利用了volatile的“顺序性”,保证对instance的写不会发生指令重排而引发其他线程获取到未初始化完全的对象
* TODO 就算没有volatile,“可见性”也可由synchronized保证
*/
private static volatile DoubleCheckSingleton instance = null;
private DoubleCheckSingleton() { }
public static DoubleCheckSingleton getInstance() {
if (null == instance) {
synchronized (DoubleCheckSingleton.class) {
if (null == instance) {
// new DoubleCheckSingleton() 不是原子操作,大体步骤如下
// memory = allocate(); // 1:分配对象的内存空间
// ctorInstance(memory); // 2:初始化对象
// instance = memory; // 3:设置instance指向刚才分配的内存地址
// 伪代码中的2和3之间,可能会被重排序,所以如果执行顺序按1,3,2的话,线程在执行完3后时间片用完切换线程则可能出现未初始化的对象
// 如果用volatile来修饰则会产生写读屏障storeload,禁止2,3的指令重拍,从而防止出现未初始化完全的问题
// TODO 就算时间片用完切换其他线程也进入不了synchronized代码块,因为原来代码块还没执行完成,所以不会释放锁,也不会出现多次实例化
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
4、静态内部类模式(推荐使用:由JVM保证线程安全且代码简单不容易出错)
public class InnerClassSingleton {
private InnerClassSingleton() { }
private static class NestClass {
private static final InnerClassSingleton instance = new InnerClassSingleton();
}
public static InnerClassSingleton getInstance() {
// javac编译后,NestClass是一个单独的.class文件,
// 加载InnerClassSingleton.class的时候不会自动加载NestClass.class文件
// 当调用NestClass.instance的时候才会触发JVM加载NestClass类
return NestClass.instance;
}
}
5、枚举模式
跟静态内部类的原理是一样的,JVM会保证enum不能被反射并且构造器方法只执行一次,因此该单例是线程安全的
public class EnumSingleton {
private EnumSingleton() { }
private enum InnerEnum {
/**
* 占位枚举值
*/
enumFactory;
private EnumSingleton instance;
/**
* TODO 注意这里是枚举类的构造器
*/
private InnerEnum() {
instance = new EnumSingleton();
}
public EnumSingleton getInstance() {
return instance;
}
}
public static EnumSingleton getInstance() {
return InnerEnum.enumFactory.getInstance();
}
}