zoukankan      html  css  js  c++  java
  • 单例模式正确使用方式

    这次分享我们就来谈谈单例模式的使用,其实在本公众号设计模式的第一篇分享就是单例模式,为什么又要讨论单例模式了?主要是那篇文章谈的比较浅,只对单例模式的主要思想做了一个分享,这篇文章会从多个方面去分享单例模式的使用,下面进入正题。

    使用Java做程序的小伙伴都知道单例,尤其是使用spring框架做项目的,大家都知道spring框架管理类默认都是单例模式的,并且是线程安全的。那么如果保证一个类只被初始化一次且线程安全了?带着这个问题我们开始这篇文章。

    在刚接触设计模式的时候,大多数人首先接触到的第一个设计模式就是单例模式,当然大多数人都只停留在单例模式,在平时的开发中用的少之又少,这篇文章会帮助大家从多个方面理解单例模式,此文代码较多,如果对单例模式概念不是很清楚的小伙伴可以看另一篇文章《设计模式之单例模式》。

    1. 懒汉模式

    package com.study.concurrency.base_concurrency.example.singleton;
    
    /**
     * <p>Title: Singleton01</p >
     * <p>Description: 懒汉模式  单例实例在第一次使用时进行创建 </p >
     * <p>Company: http://www.yinjiedu.com</p >
     * <p>Project: concurrency</p >
     *
     * @author: qiwei
     * @Date: 2019/8/20 22:41
     * @Version: 1.0
     */
    public class Singleton01 {
    
        private Singleton01() {
    
        }
    
        //单例对象
        private static Singleton01 instance = null;
    
        /**
         * @description: 静态的工厂方法
         * @auther: qiwei
         * @date: 2019/8/20 22:43
         * @return: Singleton01
         */
        public static Singleton01 getInstance() {
            if (instance == null) {
                instance = new Singleton01();
            }
            return instance;
        }
    }
    

    上述代码在单线程下是没有问题的,但是在多线程情况下是线程不安全的,出现问题的代码主要是在下面这段代码:

    2. 饿汉模式

    为了解决上面懒汉模式线程不安全问题,我们可以使用饿汉模式创建类实例。饿汉模式是单例实例在类装载时进行创建,代码如下:

    package com.study.concurrency.base_concurrency.example.singleton;
    
    /**
     * <p>Title: Singleton02</p >
     * <p>Description: 饿汉模式</p >
     * <p>Company: http://www.yinjiedu.com</p >
     * <p>Project: concurrency</p >
     *
     * @author: qiwei
     * @Date: 2019/8/20 22:53
     * @Version: 1.0
     */
    public class Singleton02 {
    
        private Singleton02() {
        }
    
        //单例对象
        private static Singleton02 instance = new Singleton02();
    
        /**
         * @description: 静态的工厂方法
         * @auther: qiwei
         * @date: 2019/8/20 22:55
         * @return: Singleton02
         */
        public static Singleton02 getInstance() {
            return instance;
        }
    }
    

    饿汉模式问题

    饿汉模式虽然是线程安全的,但是在实际开发中,会有一些大家需要注意的问题。

    第一点:饿汉模式是在类装载的时候就创建的,所以如果类的构造方法里面很多复杂的处理,类装载就会比较慢,影响程序性能;

    第二点:饿汉模式创建的示例,不管在项目其他地方有没有使用,类的实例已经创建,如果不使用,会造成资源的浪费。

    基于以上两点,大家在使用饿汉模式的时候需要注意实际的使用场景。

    3. 懒汉模式之线程安全

    我们看到在使用懒汉模式创建实例时是线程不安全的,这里我们使用同步锁的方式,让懒汉模式也是线程安全的,代码如下:

    package com.study.concurrency.base_concurrency.example.singleton;
    
    /**
     * <p>Title: Singleton01</p >
     * <p>Description: 懒汉模式 </p >
     * <p>Company: http://www.yinjiedu.com</p >
     * <p>Project: concurrency</p >
     *
     * @author: qiwei
     * @Date: 2019/8/20 22:41
     * @Version: 1.0
     */
    public class Singleton03 {
    
        private Singleton03() {
    
        }
    
        //单例对象
        private static Singleton03 instance = null;
    
        /**
         * @description: 静态的工厂方法
         * @auther: qiwei
         * @date: 2019/8/20 22:43
         * @return: Singleton01
         */
        public static synchronized Singleton03 getInstance() {
            if (instance == null) {
                instance = new Singleton03();
            }
            return instance;
        }
    }
    

    这里我们使用了synchronized关键字保证了线程安全,但是在实际开发中不要这样写,因为synchronized会带来比较严重的性能开销。

    4. 懒汉模式之双重同步锁模式(线程不安全)

    使用双重同步锁实现单例模式,代码如下:

    package com.study.concurrency.base_concurrency.example.singleton;
    
    /**
     * <p>Title: Singleton01</p >
     * <p>Description: 懒汉模式 </p >
     * <p>Company: http://www.yinjiedu.com</p >
     * <p>Project: concurrency</p >
     *
     * @author: qiwei
     * @Date: 2019/8/20 22:41
     * @Version: 1.0
     */
    public class Singleton04 {
    
        private Singleton04() {
    
        }
    
        //单例对象
        private static Singleton04 instance = null;
    
        /**
         * @description: 静态的工厂方法
         * @auther: qiwei
         * @date: 2019/8/20 22:43
         * @return: Singleton01
         */
        public static  Singleton04 getInstance() {
            if (instance == null) {
                synchronized (Singleton04.class) {
                    if (instance == null) {
                        instance = new Singleton04();
                    }
                }
            }
            return instance;
        }
    }
    

    上面代码在synchronized这里启用了双重检测机制,但是这种写法在多线程情况下也是线程不安全的,这个原因是JVM和CPU在的指令重排;这里简单的介绍一下什么是计算机指令:

    Java实例化一个对象时,主要经历下面三步指令:

    第一步:分配对象内存空间

    第二步:初始化对象

    第三步:设置instance指向刚分配的内存

    知道了上面步骤,我们知道一旦在多线程情况下发生了指令重排,就会出现安全问题,例如上面的步骤123变成132

    5. 懒汉模式之双重同步锁模式(线程安全)

    使用第四种方式创建单例也是线程不安全的,要想使第四个例子线程安全,我们需要使用到一个关键字:volatile。

    代码如下:

    package com.study.concurrency.base_concurrency.example.singleton;
    
    /**
     * <p>Title: Singleton01</p >
     * <p>Description: 懒汉模式 </p >
     * <p>Company: http://www.yinjiedu.com</p >
     * <p>Project: concurrency</p >
     *
     * @author: qiwei
     * @Date: 2019/8/20 22:41
     * @Version: 1.0
     */
    public class Singleton05 {
    
        private Singleton05() {
    
        }
    
        //单例对象
        private volatile static Singleton05 instance = null;
    
        /**
         * @description: 静态的工厂方法
         * @auther: qiwei
         * @date: 2019/8/20 22:43
         * @return: Singleton01
         */
        public static Singleton05 getInstance() {
            if (instance == null) {
                synchronized (Singleton05.class) {
                    if (instance == null) {
                        instance = new Singleton05();
                    }
                }
            }
            return instance;
        }
    }
    

    volatile可以禁止指令重排,所以上面方式是线程安全的。volatile使用场景主要是:状态标示量、双重检测。

    6. 饿汉模式之静态域实现(错误写法)

    饿汉模式第一种实现使用了直接初始化方式,这里我们可以使用静态域实现,这也是在平时开发中比较常见的一种方式,但是这里有一个坑,先埋给大家,代码如下:

    package com.study.concurrency.base_concurrency.example.singleton;
    
    /**
     * <p>Title: Singleton02</p >
     * <p>Description: 饿汉模式</p >
     * <p>Company: http://www.yinjiedu.com</p >
     * <p>Project: concurrency</p >
     *
     * @author: qiwei
     * @Date: 2019/8/20 22:53
     * @Version: 1.0
     */
    public class Singleton06 {
    
        private Singleton06() {
    
        }
    
        static {
            instance = new Singleton06();
        }
        //单例对象
        private static Singleton06 instance = null;
    
        /**
         * @description: 静态的工厂方法
         * @auther: qiwei
         * @date: 2019/8/20 22:55
         * @return: Singleton02
         */
        public static Singleton06 getInstance() {
            return instance;
        }
    }
    

    上面静态域单例模式是有问题的,大家想想有什么问题,在最后我们再讨论,大家也可以debug调试找找问题。

    7. 单例模式之枚举创建

    在前面集中创建单例的例子中,我们接触了懒汉模式、饿汉模式。个人认为不管是懒汉模式还是饿汉模式,都不是最好的方案,这里我推荐使用枚举类创建单例,既保证了线程安全的需求,又保证了资源不被浪费。代码实现如下:

    package com.study.concurrency.base_concurrency.example.singleton;
    
    /**
     * <p>Title: Singleton01</p >
     * <p>Description: 枚举模式 </p >
     * <p>Company: http://www.yinjiedu.com</p >
     * <p>Project: concurrency</p >
     *
     * @author: qiwei
     * @Date: 2019/8/20 22:41
     * @Version: 1.0
     */
    public class Singleton07 {
    
        //私有构造函数
        private Singleton07() {
    
        }
    
        //单例对象
        private static Singleton07 instance = null;
    
    
        public static Singleton07 getInstance() {
            return Singleton.INSTANCE.getInstance();
        }
    
        private enum Singleton {
            INSTANCE;
    
            private Singleton07 singleton;
    
            Singleton() {
                singleton = new Singleton07();
            }
    
            public Singleton07 getInstance() {
                return singleton;
            }
        }
    }
    

    上面代码,在使用枚举构造方法的时候JVM保证这个方法绝对只调用一次,个人推荐这种创建单例的模式。

    解决6:饿汉模式之静态域实现问题

    第六种饿汉模式创建单例的方式,主要是因为代码位置问题引起的,正确写法是把下面两段代码交换位置:

    private static Singleton06 instance = null;
    
    static {
        instance = new Singleton06();
    }
    

    这是在使用静态域时候特别容易犯的错误,希望大家注意。

    关于单例模式的分享就到这里,不足之处希望大家提出来,我会及时修正。

      获取实时信息,关注公众号:『编程之艺术』,二维码:

      

      

      

      

      

      

      

  • 相关阅读:
    IOC(控制反转)
    JQuery中的DOM操作
    【JQuery的选择器】
    JPA基本注解介绍
    JPA使用的HelloWorld
    JPA的介绍
    JQuery简介及HelloWorld
    SpringMvc处理post请求乱码的filter
    Sping3.0版本+Quartz完成定时任务
    CentOS下安装Subversion (SVN)
  • 原文地址:https://www.cnblogs.com/winqi/p/11402986.html
Copyright © 2011-2022 走看看