单例模式的几种写法
1、懒汉模式
懒汉模式,顾名思义就是很懒,要等到第一次调用的时候才创建。
public class Lazy {
private static Lazy INSTANCE = null;
private Lazy() {
}
public static Lazy getInstance() {
if (INSTANCE == null) {
INSTANCE = new Lazy();
}
return INSTANCE;
}
}
看代码,可以看出INSTANCE在创建的时候是线程不安全的,在多线程情况下会创建多个Lazy对象。
2、加锁的懒汉模式
既然懒汉模式线程不安全,那就给它加个锁来保证线程安全。
public class Lazy {
private static Lazy INSTANCE = null;
private Lazy() {
}
public static Lazy getInstance() {
if (INSTANCE == null) {
synchronized (Lazy.class) {
if (INSTANCE == null) {
INSTANCE = new Lazy();
}
}
}
return INSTANCE;
}
}
在第一次创建的时候加个synchronized同步。那这样就没有问题了吗?
我们来看看上面的new Lazy()在JVM中发生了什么。理想的代码如下:
memory = allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
INSTANCE = memory; //3:使INSTANCE指向刚分配的内存地址
CPU会将不满足happens-before原则的指令进行重排——指令重排(Google一下就知道了)。所以上述指令可能会被分配成:
memory = allocate(); //1:分配对象的内存空间
INSTANCE = memory; //3:使INSTANCE指向刚分配的内存地址
ctorInstance(memory); //2:初始化对象
上面代码可以看出,INSTANCE指向内存地址比初始化对象先行发生了,这样导致INSTANCE的值不为null。如果此时有另一个线程走到if (INSTANCE == null)判断时,此时INSTANCE不为空,但是未进行初始化,从而导致系统异常。
如果能让CPU不对指令进行重排?使用volatile关键字,可以阻止写操作指令重排。
3、双重锁的懒汉模式
public class Lazy {
private volatile static Lazy INSTANCE = null;
private Lazy() {
}
public static Lazy getInstance() {
if (INSTANCE == null) {
synchronized (Lazy.class) {
if (INSTANCE == null) {
INSTANCE = new Lazy();
}
}
}
return INSTANCE;
}
}
4、饿汉模式
public class Hungry {
private static Hungry INSTANCE = new Hungry();
private Hungry() {
}
public static Hungry getInstance() {
return INSTANCE;
}
}
饿汉非常饿,所以在类的加载过程中就已经把对象实例好了。在类加载过程中,classloader 机制保证了加载类只有一个线程,这就意味着只有一个线程去创建实例,这种模式是天然的线程安全,但是也存在一个资源浪费的问题,如果INSTANCE一直没有使用,则是浪费。
5、 静态内部类模式
public class InnerStaticClass {
private static class Holder {
private static final InnerStaticClass INSTANCE = new InnerStaticClass();
}
private InnerStaticClass() {
}
public static InnerStaticClass getInstance() {
return Holder.INSTANCE;
}
}
该模式与饿汉模式的区别在于用静态内部类来实例化单例,而静态内部类的加载是在第一次显示调用的时候才会进行加载,因此可以解决INSTANCE一直没有使用而带来的资源浪费问题。
6、 枚举模式
public enum EnumInstance {
INSTANCE;
public void printOut(){
System.out.println("枚举模式");
}
}
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。这种方式是 《Effective Java》 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。