1.饿汉式
//不允许被继承
public final class SingletonHungry {
private static SingletonHungry instance = new SingletonHungry();
private SingletonHungry() {
}
public static SingletonHungry getInstance() {
return instance;
}
}
饿汉式的关键在于如果使用这个单例类,那么instance会直接被创建,包括其中的实例对象。总的来说,其线程安全行建立在JVM对类的加载是保证线程安全的。即保证类只会被加载一次。
这样的有一个缺陷就是如果instance被实例化的同时,类中同时存在很多其他资源也同时被实例化了,那么就会占用很多系统资源。所以这个时候就需要懒汉式。
2.懒汉式
public final class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy(){}
public static SingletonLazy getInstance(){
if (null==instance) instance = new SingletonLazy();
return instance;
}
}
虽然说会判断一次instance是否会被实例化,但在多线程下不会保证instance的唯一性。
如下例子就可以证明:
线程a正在创建的过程中,此时线程b进来了判断null==instance是成立的,因为线程a并没有创建好,那么线程b也会再来创建一次。而线程b创建过程中,线程a的instance引用可能已经被其他方法捕获,那么系统堆内存里就可能同时存在两个instance实例。
3.同步懒汉式
public final class SingletonLazySync {
private static SingletonLazySync instance;
private SingletonLazySync(){}
public static synchronized SingletonLazySync getInstance(){
if (null==instance) instance = new SingletonLazySync();
return instance;
}
}
介于synchronized的性能,可能这个getInstance方法在系统内是十分频繁的,那么每次获取都需要进行一次加锁解锁,性能十分的底下。
4.Double Check
public final class SingletonDoubleCheck {
private static SingletonDoubleCheck instance;
private SingletonDoubleCheck() {
}
public static synchronized SingletonDoubleCheck getInstance() {
if (null == instance) {
synchronized (SingletonDoubleCheck.class) {
if (null == instance)
instance = new SingletonDoubleCheck();
}
}
return instance;
}
}
当两个线程发现null=instance时,只有一个线程能够进入同步块,完成对instance的实例化,等一个线程完成创建后其他线程才会有机会进入同步块进行创建,而此时instance已经被创建好了。等下一次获取的时候,就不需要进入同步块了。这样在性能上就十分的优秀,基本上就是只给创建过程加一个同步操作。只需要保证只创建一次就够了。
但是这种情况下仍然存在空指针的情况,其原因是多线程下的指令重排序造成的。
如下例子可以解释:
线程a 进入创建instance过程,可能这个创建有点耗时(可能存在其他成员需要初始化),并且instance其他成员变量还没有初始化好,这个时候线程b进入getInstance,发现instance不为空,那么就可以直接跳过这个过程,而获取到instance的引用,这时候b线程又需要应用引用到其他成员,而其他成员a线程并没有初始化好,那么就会出现空指针的异常。
这个instance已经实例化好,而instance其他成员变量还没有实例化好的情况,存在于并没有显式的happens-before关系进行约束,且JVM存在指令重排序的情况。
5.Volatile Double Check
public final class SingletonSingletonDoubleCheck {
private volatile static SingletonSingletonDoubleCheck instance;
private SingletonSingletonDoubleCheck() {
}
public static synchronized SingletonSingletonDoubleCheck getInstance() {
if (null == instance) {
synchronized (SingletonSingletonDoubleCheck.class) {
if (null == instance)
instance = new SingletonSingletonDoubleCheck();
}
}
return instance;
}
}
这种方法是借助于Volatile的强行禁止指令重排序来实现的。
6.Holder方式
public final class SingletonHolder {
private SingletonHolder() {
}
private static class Holder{
private static SingletonHolder instance= new SingletonHolder();
}
public static SingletonHolder getInstance() {
return Holder.instance;
}
}
在SingletonHolder类的初始化过程中并不会创建SingletonHolder的实例,只有在Holder被主动引用的时候,则instance会被创建。instance实例的创建过程在JAVA程序编译时期收集至<\clinit>()方法中,该方法是同步方法,而同步方法可以保证内存的可见性,JVM指令的顺序性和原子性。该单例方法是十分优秀的设计。
7. 枚举
public enum SingletonEnum {
INSTANCE;
SingletonEnum() {
System.out.println("INSTANCE 马上会被实例化");
}
public static SingletonEnum getInstance() {
return INSTANCE;
}
}
枚举类型不允许被继承,同样是线程安全的且只能被实例化一次。
防止序列化对单例模式的破坏
为了使一个单例类变成可串行化的,仅仅在声明中添加“implements Serializable”是不够的。因为一个串行化的对象在每次返串行化的时候,都会创建一个新的对象,而不仅仅是一个对原有对象的引用。为了防止这种情况,可以在单例类中加入readResolve 方法。
//在序列化完成后readResolve返回的对象将替代readObject的
private Object readResolve() {
return singleton;
}