第一种:饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
这是实现一个安全的单例模式的最简单粗暴的写法,这种实现方式我们称之为饿汉式。之所以称之为饿汉式,是因为肚子很饿了,想马上吃到东西,不想等待生产时间。这种写法,在类被加载的时候就把Singleton实例给创建出来了。
饿汉式的缺点就是,可能在还不需要此实例的时候就已经把实例创建出来了,没起到lazy loading的效果。优点就是实现简单,而且安全可靠。
第二种:双重检查模式
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
推荐理由:
- 延迟初始化。和懒汉模式一致,只有在初次调用静态方法getSingleton,才会初始化signleton实例。
- 性能优化。同步会造成性能下降,在同步前通过判读singleton是否初始化,减少不必要的同步开销。
- 线程安全。同步创建Singleton对象,同时注意到静态变量singleton使用volatile修饰。
为什么要使用volatile修饰?
虽然已经使用synchronized进行同步,但是在创建对象时,会有下面的伪代码:
memory=allocate(); //1:分配内存空间
ctorInstance(); //2:初始化对象
singleton=memory; //3:设置singleton指向刚排序的内存空间
当线程A在执行上面伪代码时,2和3可能会发生重排序,因为重排序并不影响运行结果,还可以提升性能,所以JVM是允许的。如果此时伪代码发生重排序,步骤变为1->3->2,线程A执行到第3步时,线程B调用getsingleton方法,在判断singleton==null时不为null,则返回singleton。但此时singleton并还没初始化完毕,线程B访问的将是个还没初始化完毕的对象。当声明对象的引用为volatile后,伪代码的2、3的重排序在多线程中将被禁止!
第三种:静态内部类模式
public class Singleton {
private static class SingletonInner {
private static final Singleton singleton = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonInner.singleton;
}
}
推荐理由:
- 实现代码简洁。和双重检查单例对比,静态内部类单例实现代码简洁明了。
- 延迟初始化。静态内部类不会在Singleton类加载时就加载,而是在调用getInstance()方法时才进行加载,达到了懒加载的效果
- 线程安全。JVM在执行类的初始化阶段,通过加锁来确保类的 < clinit > 方法仅被执行一次,所以只会实例化一个Singleton对象
第四种:枚举模式
以上几种单例模式,序列化和反序列化以及反射对其都是有破坏的。采用枚举则不会
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("doSomething");
}
}
直接通过Singleton.INSTANCE.doSomething()的方式调用即可。方便、简洁又安全。
参考链接: