单例模式正确使用方式

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

使用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();
}

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

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

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

  

  

  

  

  

  

  

原文地址:https://www.cnblogs.com/winqi/p/11402986.html

时间: 2024-10-28 22:02:41

单例模式正确使用方式的相关文章

iOS9使用提示框进行文本输入的正确实现方式

我在之前写过一篇博客<iOS9使用提示框的正确实现方式>,主要讲了如何使用UIAlertController替换UIAlertView进行提示框的实现.今天我们将会来实现一下在提示框中如何进行文本输入.该功能可以让用户进行密码确认等功能. 实现代码如下: #import "SecondViewController.h" #import "AppDelegate.h" @interface SecondViewController () @end @imp

关于格子之表白的正确打开方式

飞碟说69期:<表白的正确打开方式>优酷视频在线观看.你可能想不通, 我身高177.体重117.长相不错.家庭不错.成绩好.体育好.器大活还好,为啥她不喜欢我?飞碟君教你三招: 知己知彼才好下套.自恋和自卑是表白大忌.再牛的技术也比不上真情流露 .自古表白多白表,要经历多少次“十动然拒”,才能学会表白的正确打开方式? 飞碟说谈恋爱系列: 表白的正确打开方式 文字版台词 闽江学院男生赖国森向他心仪的蕾蕾,送出了一封212天写的16万字的情书,内容是回忆两人一起吃过的沙县,他将其装订成册,并取名&

单例模式(懒汉方式和饿汉方式)

Singleton 单例模式(懒汉方式和饿汉方式) 单例模式的概念: 单例模式的意思就是只有一个实例.单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个类称为单例类. 关键点: 1)一个类只有一个实例       这是最基本的 2)它必须自行创建这个实例 3)它必须自行向整个系统提供这个实例 -----------------------------------------------------------------------------------------

@synthesize的正确使用方式

@synthesize的正确使用方式 一. @synthesize的错误使用方式 类1和类2是继承关系, name是类1的属性 但是类2的实现里加入了@synthesize name = _name; 导致类1的setName name 方法都被重写了 调用结果: 没有打印任何语句 类1: #import <Foundation/Foundation.h> @interface MyTestObj : NSObject @property (nonatomic, strong) NSStrin

OSChina 周日乱弹 —— 旅游被宰后正确处理方式

怎么小小编上了三天班感觉像上了一个月的班一样,难道这是假期综合症的后遗症?要不是因为穷,本人才不会每天上班写乱弹呢! 人民币,一种生活在中国地区,飞行速度极快的鸟类动物,一般在人们不经意间,悄悄从钱包里飞走via:三吼君 土豪炫富的方式总是充满了创新 @笨笨猴:一个姑娘把新买的iPhone6s放在钢琴上,她同学看到后说:"不就是买个苹果吗,放这么显眼的位置装?"女孩笑了笑说:"我弹着80w的钢琴,你却只 看到一个8000块钱的手机."女孩的妈妈对女儿说:"

Java流的正确关闭方式

因为流是无论如何一定要关闭的,所以要写在finally里.如下: BufferedReader reader = null; try { reader = (BufferedReader) getReaderFromFile(file); …… } catch (IOException e) { throw e; } finally { if (reader != null) { try { reader.close(); } catch (IOException e){ throw e; }

设计模式—单例模式2&#183;实现方式

前言 前面介绍了单例模式是什么东东,并且在最后让Student类实现了单例模式.但是,单例模式实现的方式不仅仅只有单例模式1中演示的那一种,其实方式有很多.这里介绍常用的几种单例模式的实现方式: 1.饿汉式 2.懒汉式 3.懒汉式的进阶方式--双重验证 上面三个名词听不懂不要紧,先有个印象就行,下面听我慢慢扯-- 一.饿汉式 在单例模式1中介绍的那种实现方式就被称作为饿汉式,当Student类被加载到内存中的时候,我们创建的这个单例(Student类的对象)就已经被创建完成了.下面是这种实现方式

Xcode 的正确打开方式——Debugging(转载)

Xcode 的正确打开方式——Debugging 程序员日常开发中有大量时间都会花费在 debug 上,从事 iOS 开发不可避免地需要使用 Xcode.这篇博客就主要介绍了 Xcode 中几种能够大幅提升代码调试效率的方式. “If debugging is the process of removing bugs, then programming must be the process of putting them in.”——Edsger W. Dijkstra 添加条件 有时候我们可

swf文件的正确打开方式

软件下载链接在此,可不必看下面的解释 最近在看新东方的英语口语视频教程(<新东方4+1英语口语教程>,也在此向大家推荐一下),可是在播放过程中,老是出现自己跳转到视频的起始位置并暂停的状况,很让人抓狂,我试了好几款视频播放器,结果都是一样.想着可能是视频文件的问题吧,也就没管它了,迁就着看了几集. 可是,第二天我贼心不死,百度之.在筛掉几个错误答案之后,发现了这款ZzFlash播放器,下载试了下,视频果然不跳了. 原来视频跳转的原因是:课程设定有跟读的内容环节,一般的视频播放器没有识别出这个东