Java设计模式——单例模式

单例模式由于只创建了唯一对象可以避免资源的多重占用,减少内存的开销,对于经常性使用对象的类来说,单例是一个不错的选择,使用场景,比如:文件操作、共享资源等等。

1、饿汉单例模式

这是最为简单的也是最基本的单例模式,相信大家都写过!先上代码:

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

由于把构造函数变为了private,所以要想获得该类的实例需要通过getInstance(),而不是手动去new一个,这仅仅是最为简单粗暴的单例模式。

2、懒汉单例模式

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

懒汉单例模式看起来和饿汉单例模式差不多,就多了一个 synchronized 关键字,也就是说该模式是同步的方法单例。在 getInstanc()方法中,可以清楚知道不管该类对象是否已经实例了(实际上第一次调用的时候只new了一次),都会进行同步,这样确实每次都同步确实耗费了不必要的资源和内存,而且在加载的时候都会事先 synchronized,所以会比较耗时间,所以不太建议使用。

3、Double Check Lock(DCL)

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

在代码中可以看到,在调用 getInstance() 方法的时候会检查类的实例是否为空,true则直接返回该实例,false则会 synchronized (利用Single.class来对本类对象同步),如果为null则会new一个对象,否则直接返回。这意思很清楚,这里有两次判断是否为null的步骤,第一步判断是为了避免不必要的同步,而第二步则是同步检查。
  我们知道在new,即instance = new Single()的时候,一般会有三个步骤:1、分配内存;2、调用构造函数并初始化成员字段;3、为对象指向分配好的空间。而Java是允许处理器乱序执行的,还有JDK版本原因,很多时候这三个步骤很可能不是按照顺序执行的,
所以在多线程的操作下,如果在A线程中执行某一步的时候,B线程也调用了该方法,而这时候A线程刚刚好分配内存成功(假设第一个调用)但未调用构造函数创建对象,所以这时候B在检查的时候对象已经非空了(因为已经指向了内存),所以B线程会直接使用对象instance,很显然,由于还没调用构造函数,所以B线程使用的时候会出问题。这叫DCL失效。虽然说这是很小的概率问题,但还是会长期隐藏着问题的。不过从JDK1.5之后,sun注意了这个问题,把这个bug修改了过来,只要 如此声明:private volatile static Single instance = null 就可以保证instance 是从主内存取出来的,虽然volatile 会影响性能,但为了DCL有效就值得。
  总的来说DCL是饿汉、懒汉单例模式的结合,尽管存在bug,但还是sun已修改了,所以比较建议这种写法。

4 、内部静态类单例模式

  利用静态内部类的特性来返回对象(static关键字不用多讲了吧)

public class Single {

    private Single (){}
    public static Single getInstance(){
        return SingleHolder.instance;
    }
    private static class SingleHolder{
        private static final Single  instance = new Single();
    }

}

  这不仅仅首次调用才初始化,而且线程安全,也能保证对象的唯一性,所以这也是推荐的写法。

5、枚举单例模式

public enum  SingleEnum {
    INSTANCE;
}

在Java中,枚举类型是默认线程安全的,在任何情况下都能保证唯一性,而且写法最为简单。大家可以尝试一下的

6、容器单例模式

public class SingleCollect {
    private static Map<String, Object> objectMap = new HashMap<>();
    private SingleCollect(){}

    /**
     * 根据类名把实例通过Map保存起来
     * @param key  类名
     * @param instance  类的实例对象
     */
    public static void registInatance(String key, Object instance){
        if (!objectMap.containsKey(key)){
            objectMap.put(key, instance);
        }
    }

    /**
     * 根据类名来寻找对应的对象实例
     * @param key
     * @return
     */
    public static Object getInstance(String key){
        return objectMap.get(key);
    }
}

  利用Map把类的对象实例保存起来,在需要的时候根据类名key获取即可。

总结

  通过几种单例模式,核心思想无非是把构造函数私有化,然后利用 public static修饰符来获取对象实例,并且保证线程安全!!!值得注意的时候,我们还需要考虑这么一种情况:反序列化。我们知道可以通过序列化把对象实例写进磁盘,然后再读回来。而反序列化依然可以通过别的途径去重新创建一个新的对象实例,即便是私有的构造函数!上述的几种模式就只有枚举单例模式可以避免。不过,在实际开发中,我们需要结合项目需要,而不是一味地直接使用枚举单例模式,灵活使用单例模式才是正道。
  上文如有不对或者不妥之处,大家记得留言指出哈。一起进步才比较爽啊!!!and then 后续我会继续写一系列关于设计模式的,请大家静候!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 单例模式(SingletonPattern)一般被认为是最简单、最易理解的设计模式,也因为它的简洁易懂,是项目中最...
    成热了阅读 9,734评论 4 34
  • 概念 java中单例模式是一种常见的设计模式,单例模式的写法有好几种,比较常见的有:懒汉式单例、饿汉式单例。单例模...
    怡红快绿阅读 3,311评论 0 0
  • Java设计模式——单例模式 单例模式应该是大家最为熟知的一种设计模式了,相信大家或多或少的都在自己的项目中使用过...
    gogoingmonkey阅读 3,452评论 0 2
  • 今天笔试的时候被问到了单例模式,听了很多次,但是却没有认真看过,所以交了白卷,懒的教训啊!还有一题比较有趣的题目:...
    shakesbears阅读 2,833评论 0 3
  • 阅读原文 在介绍单例模式之前,我们先了解一下,什么是设计模式?设计模式(Design Pattern):是一套被反...
    gyl_coder阅读 1,209评论 0 3