设计模式 - 单例模式的7种实现

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;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值