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

Java 设计模式——单例模式

作者: 斌林诚上 | 来源:发表于2018-11-08 22:15 被阅读7次

    一、简介

    属于创建型模式,提供了一种创建对象的最佳方式。
    这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。
    这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

    注意:

    1、单例类只能有一个实例。
    2、单例类必须自己创建自己的唯一实例。
    3、单例类必须给所有其他对象提供这一实例。

    二、作用

    意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

    主要解决:一个全局使用的类频繁地创建与销毁。

    关键代码:构造函数是私有的。

    优点:

    1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
    2、避免对资源的多重占用(比如写文件操作)。

    缺点:

    1、没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
    2、如果实例化的对象长时间不被利用,会被系统认为是垃圾而被回收,这将导致对象状态的丢失。

    三、实现方式

    说明

    实现方式:根据需求场景,可分为2大类、6种实现方式。
    两大类:饿汉式,懒汉式
    饿汉式:单例创建时机不可控,即类加载时 自动创建 单例
    懒汉式:单例创建时机可控,即有需要时,才手动创建 单例

    (1)第一种方式

    懒汉式 线程不安全

    /**
     *  不支持多线程,因为没有加锁 synchronized
     */
    public class SingletonI {
    
        //声明单例的引用
        private static SingletonI instance;  
        //创建私有构造函数,防止创建新的实例
        private SingletonI (){}  
        //创建单例
        public static SingletonI getInstance() {  
            //判断实例是否为空
            if (instance == null) {
                //创建实例
                instance = new SingletonI();
            }
            //对外提供的唯一实例
            return instance;  
        } 
    }
    

    (2)第二种方式

    懒汉式 线程安全

    /**
     *  在第一种方式的基础上添加了synchronized
     *  必须加锁 synchronized 才能保证线程安全,
     *   缺点:每次访问都要进行线程同步(即 调用synchronized锁),造成过多的同步开销(加锁 = 耗时、耗能)
     */
    public class SingletonII {
        //声明单例的引用
        private static SingletonII instance;
        //创建私有构造函数,防止创建新的实例
        private SingletonII (){}  
        //添加了synchronized 在多线程下保证线程安全
        public static synchronized SingletonII getInstance() {  
            if (instance == null) {  
                instance = new SingletonII();  
            }  
            return instance;  
        }
    }
    

    (3)第三种方式

    饿汉式 线程安全

    /**
     *  单例对象 要求初始化速度快 & 占用内存小单例对象 要求初始化速度快 & 占用内存小
     *  优点:没有加锁,执行效率会提高。
     *  缺点:类加载时就初始化,浪费内存。
     */
    public class SingletonIII {
    
        //加载该类时,立马创建实例
        private static SingletonIII instance = new SingletonIII();  
        //创建私有构造函数,防止创建新的实例
        private SingletonIII (){}  
        //提供一个访问该实例的访问点
        public static SingletonIII getInstance() {  
            return instance;  
        }
    }
    

    (4)第四种方式

    懒汉式 双检锁/双重校验锁

    /**
     *   这种方式采用双锁机制,安全且在多线程情况下能保持高性能
     *   缺点:实现方式较复杂
     */
    public class SingletonIV {
        //声明单例的引用
        private static SingletonIV instance;
        //创建私有构造函数,防止创建新的实例
        private SingletonIV() {}
        //创建线程安全的单例
        public static SingletonIV getInstance() {
            //判断是否存在单例
            if(instance==null) {
                //保持只有一个线程执行
                synchronized(SingletonIV.class) {
                    //再次判断单例是否被创建(防止其他线程已经创建而导致再次创建)
                    if(instance==null) {
                        instance=new SingletonIV();
                    }
                }
            }
            return instance;
        }
    
    }
    

    (5)第五种方式

    懒汉式 登记式/静态内部类

    /**
     *  这种方式能达到双检锁方式一样的功效,但实现更简单
     */
    public class SingletonV {
        //创建静态内部类、创建单例
        private static class SingletonHolder {  
            private static final SingletonV INSTANCE = new SingletonV();  
        }  
        //创建私有构造函数,防止创建新的实例
        private SingletonV (){}  
        //提供一个外部访问点来获取单例
        public static final SingletonV getInstance() {  
            return SingletonHolder.INSTANCE;  
        }
    }
    

    (6)第六种方式

    枚举

    /**
     *  创建单例、线程安全、实现简洁的需求
     */
    public enum SingletonVI {
         //定义1个枚举的元素,即为单例类的1个实例
        INSTANCE;  
        // 隐藏了1个空的、私有的 构造方法
        // private Singleton () {}
    
        // 获取单例的方式:
        //Singleton singleton = Singleton.INSTANCE;
    }
    

    般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。
    只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。
    如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。
    如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

    四、测试

    测试调用方式

        //用于测试,在每个单例添加如下方法
        public void showData() {
            System.out.println(getClass());
        }
    

    测试类

    public class TestSingleton {
        public static void main(String[] args) {
            //调用方式
            SingletonI instance = SingletonI.getInstance();
            SingletonII instanceII = SingletonII.getInstance();
            SingletonIII instanceIII = SingletonIII.getInstance();
            SingletonIV instanceIV = SingletonIV.getInstance();
            SingletonV instanceV = SingletonV.getInstance();
            SingletonVI instanceVI = SingletonVI.INSTANCE;
            instance.showData();
            instanceII.showData();
            instanceIII.showData();
            instanceIV.showData();
            instanceV.showData();
            instanceVI.showData();
        }
    }
    

    结果:除了枚举,其他单例的调用方式都差不多一致

    image.png

    测试单例模式效果

    举例:假设一家人都在使用一张信用卡(单例对象),消费与还款都在同一张信用卡上。

    /**
     * 创建信用卡单例
     * 这里采用枚举实现方式 
     */
    public enum Card {
        INSTANCE;
    
            //初始金额
        private int Amount = 20000;
        /**
         * 消费
         */
        public void consume(int sum) {
            System.out.println("消费了"+sum);
            this.Amount-=sum;
        }
        /**
         *  还款
         */
        public void repayment(int sum) {
            System.out.println("还款了"+sum);
            this.Amount+=sum;
        }
        /**
         *  查询
         */
        public void query() {
            System.out.println("信用卡额度:"+Amount);
        }
    }
    

    测试类:

    public class TestSingleton {
        public static void main(String[] args) {
            //测试是否实现单例
            //举例:假设一家人都在使用一张信用卡(单例对象),消费与还款都在同一张信用卡上。
            //步骤一:创建一张信用卡实例(单例) 查询信用卡额度
            Card card = Card.INSTANCE;
            card.query();
            //假设你在学校(另一个类中)消费了5000
            System.out.println("我");
            Card card1 = Card.INSTANCE;
            card1.consume(5000);    
            card1.query();
            //假设母亲在家里(另一个类中)消费了7000
            System.out.println("母亲");
            Card card2 = Card.INSTANCE;
            card2.consume(7000);    
            card2.query();
            //假设父亲公司(另一个类中)还款了7000
            System.out.println("父亲");
            Card card3 = Card.INSTANCE;
            card3.repayment(7000);  
            //最后查询信用卡的额度
            card.query();
            //始终使用的都是同一张卡
        }
    }   
    
    

    效果:

    image.png

    五、源码地址

    https://github.com/DayorNight/DesignPattern

    六、参考文档

    https://www.jianshu.com/p/b8c578b07fbc
    http://www.runoob.com/design-pattern/singleton-pattern.html

    七、内容推荐

    CSDN地址:https://blog.csdn.net/cs_lwb/article/details/83864571

    如果你觉得我写的不错或者对您有所帮助的话。不妨顶一个,也方便以后复习
    您的每个举动都是对我莫大的支持

    谢谢了!!! image

    相关文章

      网友评论

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

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