饿汉模式
public class Singleton {
//私有静态变量
private static Singleton instance = new Singleton();
//私有构造方法
private Singleton() {
}
//公有静态函数
public static Singleton2 getInstance (){
return instance ;
}
}
- 特点
饿汉模式在类被初始化时就已经在内存中创建了对象,以空间换时间。
不存在线程安全问题。
懒汉模式
public class Singleton {
//私有静态变量
private static Singleton instance;
//私有构造方法
private Singleton() {
}
//公有静态函数
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
- 特点
懒汉模式在方法被调用后才创建对象,实现了延时加载,以时间换空间。
线程不安全,多线程下不能正常工作。
双重锁懒汉模式
public class Singleton {
private volatile static Singleton instance;
private Singleton (){
}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class) {
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
- 特点
双重锁懒汉模式只有在对象需要被使用时才创建,第一次判断 instance == null是为了避免非必要加锁,当第一次加载时才对类加锁再实例化。这样既可以节约内存空间,又可以保证线程安全。
volatile确保instance每次都从内存中读取,而主内存是被所有线程所共享的,这里的代价就是牺牲了性能,无法利用寄存器或CPU cache。
双重锁需要注意的问题
volatile 关键字可以禁止指令重排 :可以确保instance = new Singleton()对应的指令不会重排序。
因为instance = new Singleton()可以分解为以下3行伪代码:
memory = allocate(); // 1:分配对象的内存空间
ctorInstance(memory); // 2:初始化对象
instance = memory; // 3:设置instance指向刚分配的内存地址
2和3之间重排序之后的执行时序如下。
memory = allocate(); // 1:分配对象的内存空间
instance = memory; // 3:设置instance指向刚分配的内存地址
// 注意,此时对象还没有被初始化!
ctorInstance(memory); // 2:初始化对象
假设多线程并发执行,2和3的重排序,将导致线程B判断出instance不为空,线程B接下来将访问instance引用的对象。此时,线程B将会访问到一个还未初始化的对象。
所以必须使用volatile来声明instance。
静态内部类模式
/**
* 静态内部类
*/
private static class SingletonHolder{
/**
* 静态初始化器,由JVM来保证线程安全
*/
private static Singleton instance = new Singleton();
}
/**
* 私有化构造方法
*/
private SingleTon() {
}
/**
* 这个模式的优势在于:getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本
*/
public static SingleTon getInstance() {
return SingletonHolder.instance;
}
- 特点
该内部类的实例与外部类的实例没有绑定关系,只有当getInstance()方法第一次被调用时,才会使虚拟机加载SingletonHolder类,创建对象。
静态内部类模式不仅能确保线程安全,也实现了延时加载。
枚举模式
public enum Singleton {
INSTANCE;
private Singleton() {
}
}
- 特点
枚举实现单例使用简洁、线程安全、无偿地提供了序列化机制、在面对复杂的序列化或者反射攻击时仍然可以绝对防止多次实例化。
Joshua Bloch大神在《Effective Java》中明确表达:
使用枚举实现单例的方法虽然还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。
我们定义的一个枚举,在第一次被真正用到的时候,会被虚拟机加载并初始化,而这个初始化过程是线程安全的。而我们知道,解决单例的并发问题,主要解决的就是初始化过程中的线程安全问题。
所以,由于枚举的以上特性,枚举实现的单例是天生线程安全的。