美文网首页
Java设计模式——单例模式

Java设计模式——单例模式

作者: InBinfen | 来源:发表于2017-07-28 21:36 被阅读0次

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

    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 后续我会继续写一系列关于设计模式的,请大家静候!

    相关文章

      网友评论

          本文标题:Java设计模式——单例模式

          本文链接:https://www.haomeiwen.com/subject/bgatlxtx.html