美文网首页我爱编程
JAVA设计模式(一)单例模式

JAVA设计模式(一)单例模式

作者: kakaxicm | 来源:发表于2018-06-11 19:31 被阅读0次

    引言

    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
    这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
    设计原则:
    1、单例类只能有一个实例,所以这个示例必须私有并且只能创建一次;
    2、单例类必须自己创建自己的唯一实例,所以它的构造方法必须私有;
    3、单例类必须给所有其他对象提供这一实例,提供对外的返回示例的方法,这个方法只能是静态方法,因为别人无法创建实例,从而这个实例对象也为静态属性。
    UML图如下:


    单例模式.png

    优缺点分析

    1.优点:
    1>内存中仅此一份,减少了内存的开销,尤其是频繁的创建和销毁实例,如页面缓存;
    2>避免对资源的多重占用,如多线程对文件的写操作;
    2.缺点:没有接口,不能继承,扩展性较差。
    单例模式有多种写法,这里我们学习其中的6中方法:

    饿汉式(线程安全)

    package com.qicode.kakaxicm.designpattern.singleton;
    
    /**
     * Created by chenming on 2018/6/11
     * 饿汉式,线程安全
     */
    public class HungrySingleton {
        private static HungrySingleton sInstance = new HungrySingleton();
    
        private HungrySingleton() {
        }
    
        public static HungrySingleton getsInstance() {
            return sInstance;
        }
    
        public void showMsg(String s) {
            System.out.println(this.getClass().getSimpleName() + "--" + this + "--" + s);
        }
    }
    

    这种方式在类加载时就完成了初始化,类加载较慢,单获取对象速度快。它基于类加载机制,避免了线程安全问题。如果始终没用到这个实例,则会造成内存浪费。

    懒汉式(线程不安全)

    package com.qicode.kakaxicm.designpattern.singleton;
    
    /**
     * Created by chenming on 2018/6/11
     */
    public class LazySingleton {
    
        private static LazySingleton sInstance;
        private LazySingleton() {
    
        }
    
        public static LazySingleton getsInstance(){
            if(sInstance == null){
                sInstance = new LazySingleton();
            }
            return sInstance;
        }
    
        public void showMsg(String s) {
            System.out.println(this.getClass().getSimpleName() + "--" + this + "--" + s);
        }
    }
    

    这种方式在首次使用的时候才构造实例,节约资源,多线程不安全。

    懒汉式(线程安全)

    package com.qicode.kakaxicm.designpattern.singleton;
    
    /**
     * Created by chenming on 2018/6/11
     */
    public class LazySingleton {
    
        private static LazySingleton sInstance;
        private LazySingleton() {
    
        }
    
        public static synchronized LazySingleton getsInstance(){
            if(sInstance == null){
                sInstance = new LazySingleton();
            }
            return sInstance;
        }
    
        public void showMsg(String s) {
            System.out.println(this.getClass().getSimpleName() + "--" + this + "--" + s);
        }
    }
    

    在懒汉式的基础上,对getsInstance加了同步,解决线程安全问题,但是大部分情况下,用不到同步,影响效率,所以不建议这种方式。

    双重锁(DCL)

    package com.qicode.kakaxicm.designpattern.singleton;
    
    /**
     * Created by chenming on 2018/6/11
     */
    public class DCLSingleton {
        private static DCLSingleton sInstance;
    
        private DCLSingleton() {
    
        }
    
        public static DCLSingleton getsInstance() {
            if (sInstance == null) {
                synchronized (DCLSingleton.class) {
                    if(sInstance == null){
                        sInstance = new DCLSingleton();
                    }
                }
            }
            return sInstance;
        }
    
        public void showMsg(String s) {
            System.out.println(this.getClass().getSimpleName() + "--" + this + "--" + s);
        }
    }
    

    第一次判空是为了避免不必要的同步,第二次的判空和构建实例为原子操作,解决了多线程安全问题。这种方法只在第一次使用时稍慢,整体效率高,也解决了线程安全问题。不过还有比这更好的方法,那就是静态内部类。

    静态内部类单例模式

    package com.qicode.kakaxicm.designpattern.singleton;
    
    
    /**
     * Created by chenming on 2018/6/11
     */
    public class Singleton {
        private Singleton(){
    
        }
    
        private static class SingletonHolder{
            private static final Singleton sInstance = new Singleton();
        }
    
        public static Singleton getInstance(){
            return SingletonHolder.sInstance;
        }
    
        public void showMsg(String s) {
            System.out.println(this.getClass().getSimpleName() + "--" + this + "--" + s);
        }
    }
    

    静态内部类在Singleton加载时,并不会加载SingletonHolder类,因此实现懒加载,只有getInstance被调用时,SingletonHolder才会被加载,此时实例才被创建,而且因为类只被加载一次,所以也不存在线程安全问题。

    枚举法

    public enum Singleton {  
        INSTANCE;  
        public void showMsg(String s) {
            System.out.println(this.getClass().getSimpleName() + "--" + this + "--" + s);
        }
    }  
    

    枚举实例的创建线程安全,并且在任何情况下都是单例。枚举虽然简单,但是因为可读性的原因没被推广开来。

    关于反射和反序列化的单例破坏问题

    1.反射可以强制获取构造器,创建实例,可以在构造器里面添加标记,当第二次被调用时抛出异常解决。
    2.反序列化可可以创建新的对象,它提供了readResolve可以控制对象的反序列化:

    private Object readResolve(){
    return instance;
    }
    

    最后,说明一下这些方式的应用场景。一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。
    完整代码地址:设计模式学习GayHub地址

    相关文章

      网友评论

        本文标题:JAVA设计模式(一)单例模式

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