单例模式(下)---聊一聊单例模式的几种写法

在上一篇文章 单例模式(上)—-如何优雅地保证线程安全问题中,咱们采起了懒汉式写法来写咱们的单例模式,而且重点讲解了懒汉式中线程安全的问题。这篇咱们来说讲单例模式中的其余几种写法。vue

上篇文章中,方法和变量的声明都忘了加上“static”的声明,这里提醒一下。java

懒汉式

 

懒汉式在上节咱们已经讲过了,直接给出代码:安全

public class Singleton {
    private static volatile Singleton instance = null;
    private Singleton(){};

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class){
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

懒汉式这种方式须要咱们来本身加锁,保证线程安全的问题。markdown

不过就算咱们保证了线程安全,这种写法仍是没法保证存在惟一一个对象实例。由于别人仍是能够经过反射的方式来建立一个新的对象。我写个示例:app

public class Singleton {
    public static void main(String[] args) throws Exception{
        //得到构造器
        Constructor<Singleton> c = Singleton.class.getDeclaredConstructor();
        //把构造器设置为可访问
        c.setAccessible(true);
        //建立两个实例对象
        Singleton s1 = c.newInstance();
        Singleton s2 = c.newInstance();
        //比较下两个实例是否相等
        System.out.println(s1 == s2);
    }
}

打印结果:false。ui

因此懒汉式这种方式仍是存在一些缺点的。lua

饿汉式

 

所谓饿汉式,就是一开始把对象实例建立出来,而不是等getInstance这个方法被调用才来建立对象。代码以下:url

public class Singleton2 {
    private static Singleton2 instance = new Singleton2();
    //私有构造器
    private Singleton2(){};

    public static Singleton2 getInstance() {
        return instance;
    }
}

饿汉式与懒汉式相比,咱们不用管线程安全的问题,代码看起来也比较简洁。spa

可是,因为对象一开始就被建立出来了,假如咱们从头至尾都不调用getInstance()这个方法,那么这个对象就白建立了。线程

固然,和懒汉式同样,饿汉式也存在反射问题。

总结一下饿汉式的一些问题:

一、有可能出现对象白白浪费的状况。

二、和懒汉式同样,没法组织反射问题。

 

采用静态内部类的写法

直接上代码

public class Singleton3 {
    //静态内部类
    private static class LazyHolder{
        private static Singleton3 instance = new Singleton3(); 
    }
    //私有构造器
    private Singleton3(){};
    public static Singleton3 getInstance() {
        return LazyHolder.instance;
    }
}

因为外部类没法访问静态内部类,所以只有当外部类调用Singleton.getInstance()方法的时候,才能获得instance实例。

而且,instance实例对象初始化的时机并非在Singleton被加载的时候,而是当getInstance()方法被调用的时候,静态内部类才会被加载,这时instance对象才会被初始化。而且也是线程安全的。

因此,与饿汉式相比,经过静态内部类的方式,能够保证instance实例对象不会被白白浪费。

可是,它仍然存在反射问题。

 

采起枚举的方式

 

直接上代码:

public enum Singleton4 {
    //通常用大写的了,不过为了和前面的统一
    //我就用小写的了

    instance;
}

枚举的方式简单吧?一行代码就搞定了,不过和饿汉式同样,因为一开始instance实例就被建立了,因此有可能出现白白浪费的状况。

可是,经过枚举的方式,不只代码简单,线程安全,并且JVM还能阻止反射获取枚举类的私有构造器。

下面作个实验

public enum Singleton4 {
    //通常用大写的了,不过为了和前面的统一
    //我就用小写的了
    instance;

    public static void main(String[] args) throws Exception{
        //得到构造器
        Constructor<Singleton4> c = Singleton4.class.getDeclaredConstructor();
        //把构造器设置为可访问
        c.setAccessible(true);
        //建立两个实例对象
        Singleton4 s1 = c.newInstance();
        Singleton4 s2 = c.newInstance();
        //比较下两个实例是否相等
        System.out.println(s1 == s2);
    }
}

结果出现了异常:

Exception in thread “main” java.lang.NoSuchMethodException: singleton.Singleton4.()

at java.lang.Class.getConstructor0(Class.java:3082)

at java.lang.Class.getDeclaredConstructor(Class.java:2178)

at singleton.Singleton4.main(Singleton4.java:12)

因此,这种枚举的方式能够说的用的最多的一种方式了,惟一的缺点就是对象一开始就被建立,可能出现白白浪费没有用到对象的状况。

不过,整体上,仍是推荐采用枚举的方式来写。

获取更多原创文章,能够关注下个人公众号:苦逼的码农,我会不按期分享一些资源和软件等。后台回复礼包送你一份时下热门的资源大礼包。同时也感谢把文章介绍给更多须要的人。

相关文章
相关标签/搜索