java单例模式几种实现方法

java单例模式中,留意几个关键点:构造方法私有化,单例对象变量需要static修饰,借助公有方法获取单例对象。另外也需要考虑线程是否安全,是否为延迟加载。

饿汉模式

public class Hungry{
    private final static Hungry instance=new Hungry();
    private Hungry(){}
    public static Hungry getInstance(){
        return instance;
    }
}

饿汉模式可以保证线程安全,但是却不是延迟加载。这里扩展说一下延迟加载:我们知道类加载包括加载,链接和初始化阶段,而链接又分为:验证,准备和解析,对于静态变量,在准备阶段会赋值默认值,这里就是为instance赋予null,而在初始化阶段会赋值用户设定的值,这里就是new Hungry()。也就是说,一旦这个Hungry类被加载了(使用new关键字获取,反射forName()等都会触发类加载),那么内存就会分配一个空间存放new Hungry()对象,而更多地时候,我们并不是为了使用这个Hungry对象才加载这个类的,这就会占用额外的内存空间。所以引入延迟加载概念就是将初始化instance放到需要用到这个对象的时候。下面介绍的懒汉模式就可以实现延迟加载。

懒汉模式

public class Lazy{
    private static Lazy instance=null;
    private Lazy(){}
    public static Lazy getInstance(){
        if(instance==null)instance=new Lazy();
        return instance;
    }
}

懒汉模式解决了延迟加载,但是却没有考虑到线程安全,多个线程可能会同时进入if语句,导致各个线程获得的对象不同。下面列出两个改进方法:

public class Lazy{
    private static Lazy instance=null;
    private Lazy(){}
    //使用synchronized关键字修饰
    public static synchronized Lazy getInstance(){
        if(instance==null)instance=new Lazy();
        return instance;
    }
}

线程是安全了,但是也要保证效率,synchronized是互斥锁,而且还需要操作系统切换线程上下文,得不偿失。进一步优化:

/*
 * 假设线程A、B
 * Step1:A进入同步代码块
 * Step2:B进入getInstance(),发现instance==null,进入if,锁被占用,阻塞
 * Step3:A执行完同步代码块,释放锁
 * Step4:B获得锁,发现instance已实例化,退出
 * 
 * 如果不进行第二次判定,B线程进入后会再次执行instance=new DoubleLock() 导致获取不同的实例
 */
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;
    }
}

对于DCL(双重检查锁),其实存在着不少的争执,在《Java并发编程实战》中文版一书中说道:

DCL的真正问题在于:当在没有同步的情况下读取一个共享对象时,可能发生的最糟糕事情只是看到一个失效值(在这种情况下是一个空值),此时DCL方法将通过在持有锁的情况下再次尝试来避免这种风险,然而,实际情况远比这种情况糟糕——线程可能看到引用的当前值,但对象的状态值却是失效的,这以为这线程可以看到对象处于无效或错误的状态。

由于bo主技术有限,如果想要深入理解,可以参考这篇博文

延迟初始化占位类模式(瞬间高大上)

说白了,就是将单例对象放入一个私有静态内部类中,上代码:

public class Outer {
    private Outer(){}

    private static class Inner{
        private static Outer instance=new Outer();
    }

    public static Outer getInstance(){
        return Inner.instance;
    }

    public static void other(){}
}

当我们调用Outer类其他方法时,并不会加载Inner类,起到延迟初始化的作用,当且仅当我们调用getInstance()时,需要返回Inner类的静态变量,这时候类加载器才会加载我们的Inner类。这种方法是线程安全的。

枚举类型

这种方法最为简洁,同时也保证了延迟加载和线程安全。

enum EnumModel {
    INSTANCE;
    private EnumModel(){}
}

需要调用时,直接EnumModel.INSTANCE 方便快捷。

猜你喜欢

转载自blog.csdn.net/GD_Hacker/article/details/80379049