首页 > 其他分享 >设计模式 -- Singleton(单例模式)

设计模式 -- Singleton(单例模式)

时间:2022-09-28 16:14:21浏览次数:62  
标签:Singleton -- private uniqueInstance 实例 线程 static 设计模式

单例模式(Singleton)

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

在软件系统中,经常有这样一个特殊的类,必须保证它们在系统中只存在一个示例,才能确保他们的逻辑正确性、以及良好的效率。
这应该是类设计者的责任,而不是使用者的责任。

懒汉式(线程不安全)

public class Singleton {
     private static Singleton uniqueInstance;

     private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}
  • 说明: 先不创建实例,当第一次被调用时,再创建实例,所以被称为懒汉式。
  • 优点: 延迟了实例化,如果不需要使用该类,就不会被实例化,节约了系统资源。
  • 缺点: 线程不安全,多线程环境下,如果多个线程同时进入了 if (uniqueInstance == null) ,若此时还未实例化,也就是uniqueInstance == null,那么就会有多个线程执行 uniqueInstance = new Singleton(); 就会实例化多个实例。

饿汉式(线程安全)

public class Singleton {

    private static Singleton uniqueInstance = new Singleton();

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        return uniqueInstance;
    }

}
  • 说明: 先不管需不需要使用这个实例,直接先实例化好实例 (饿死鬼一样,所以称为饿汉式),然后当需要使用的时候,直接调方法就可以使用了。
  • 优点: 提前实例化好了一个实例,避免了线程不安全问题的出现。
  • 缺点: 直接实例化好了实例,不再延迟实例化;若系统没有使用这个实例,或者系统运行很久之后才需要使用这个实例,会造成操作系统的资源浪费。

懒汉式(线程安全)

public class Singleton {
    private static Singleton uniqueInstance;

    private static singleton() {
    }

    private static synchronized Singleton getUinqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}
  • 说明: 实现和 线程不安全的懒汉式 几乎一样,唯一不同的点是,在get方法上 加了一把锁。如此一来,多个线程访问,每次只有拿到锁的的线程能够进入该方法,避免了多线程不安全问题的出现。
  • 优点: 延迟实例化,节约了资源,并且是线程安全的。
  • 缺点: 虽然解决了线程安全问题,但是性能降低了。因为,即使实例已经实例化了,既后续不会再出现线程安全问题了,但是锁还在,每次还是只能拿到锁的线程进入该方法使线程阻塞,等待时间过长。

双重检查锁实现(线程安全)

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }  
}

说明: 双重检查数相当于是改进了 线程安全的懒汉式。线程安全的懒汉式 的缺点是性能降低了,造成的原因是因为即使实例已经实例化,依然每次都会有锁。而现在,我们将锁的位置变了,并且多加了一个检查。 也就是,先判断实例是否已经存在,若已经存在了,则不会执行判断方法内的有锁方法了。 而如果,还没有实例化的时候,多个线程进去了,也没有事,因为里面的方法有锁,只会让一个线程进入最内层方法并实例化实例。如此一来,最多最多,也就是第一次实例化的时候,会有线程阻塞的情况,后续便不会再有线程阻塞的问题。

为什么使用 volatile 关键字修饰了 uniqueInstance 实例变量 ?

uniqueInstance = new Singleton(); 这段代码执行时分为三步:

为 uniqueInstance 分配内存空间
初始化 uniqueInstance
将 uniqueInstance 指向分配的内存地址
正常的执行顺序当然是 1>2>3 ,但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。
单线程环境时,指令重排并没有什么问题;多线程环境时,会导致有些线程可能会获取到还没初始化的实例。
例如:线程A 只执行了 1 和 3 ,此时线程B来调用 getUniqueInstance(),发现 uniqueInstance 不为空,便获取 uniqueInstance 实例,但是其实此时的 uniqueInstance 还没有初始化。

解决办法就是加一个 volatile 关键字修饰 uniqueInstance ,volatile 会禁止 JVM 的指令重排,就可以保证多线程环境下的安全运行。

优点: 延迟实例化,节约了资源;线程安全;并且相对于 线程安全的懒汉式,性能提高了。

缺点: volatile 关键字,对性能也有一些影响。

静态内部类实现(线程安全)

public class Singleton {

    private Singleton() {
    }

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getUniqueInstance() {
        return SingletonHolder.INSTANCE;
    }
}
  • 说明: 首先,当外部类 Singleton 被加载时,静态内部类 SingletonHolder 并没有被加载进内存。当调用 getUniqueInstance() 方法时,会运行 return SingletonHolder.INSTANCE; ,触发了 SingletonHolder.INSTANCE ,此时静态内部类 SingletonHolder 才会被加载进内存,并且初始化 INSTANCE 实例,而且 JVM 会确保 INSTANCE 只被实例化一次。
  • 优点: 延迟实例化,节约了资源;且线程安全;性能也提高了。

总结

  • Singleton模式中的实例构造器可以设置为protected,以允许子类派生
  • Singleton模式一般不要支持Clone接口,因为有可能会导致多个对象实例,与Singleton模式的初衷相违背。
  • 如何实现多线程环境下安全的Singleton?注意对双检查锁的正确实现。

使用场景总结:

  • 频繁实例化然后又销毁的对象,使用单例模式可以提高性能。
  • 经常使用的对象,但实例化时耗费时间或者资源多,如数据库连接池,使用单例模式,可以提高性能,降低资源损坏。
  • 使用线程池之类的控制资源时,使用单例模式,可以方便资源之间的通信。

标签:Singleton,--,private,uniqueInstance,实例,线程,static,设计模式
From: https://www.cnblogs.com/tie-dao/p/16738440.html

相关文章

  • SQL字符串转换为数组
    思路:按指定符号分割字符串,返回分割后的元素个数,方法很简单,就是看字符串中存在多少个分隔符号,然后再加一,就是要求的结果。——返回字符串数组长度函数createfunctionGet......
  • ts报错解决-//@ts-ignore
    项目中最近切换了TypeScript,运行时没有问题的,但是打包的时候会报很多的错,最后我们是在报错的文件上方加上了 '//@ts-ignore'隐藏了ts文件的报错,建议我们在使用的时候在这......
  • 购买投影仪之前的学习阶段
    重要参数:1.亮度流明:光源的亮度。ANSI流明:投影到幕布后的亮度。(有效参数,决定了白天是否可以使用)2.分辨率4K/1080P/720P标准分辨率:物理分辨率。如何判断分辨率可以看......
  • 对equals的使用,完成对输入对象的选择
    今天也算是有纪念意义的一天——自己敲出来一个代码。本来昨天下午的时候就想把这个代码给敲出来,但是奈何基础不牢,地动山摇。搞了一下午还是没有一个满意的结果,今天重新刷......
  • 前端下拉框样式
    <tr><tdalign="right"><selectid="sortOrder"name="sortOrder"style="width:340px;">......
  • 【debug】gdb
    首先要编译出带调试信息的程序,加个-g参数 gdb程序名进入gdb模式 ulimit-cunlimied设置core文件大小为没有限制 break文件名:行号设置断点(甚至可以设置......
  • 9月28日——while循环知识不充、for循环、break及continue生效范围和range方法的介绍
    目录今日内容总结一、while循环内容补充1、死循环2、嵌套和全局标志位二、循环结构之for循环概念介绍for循环结构:1、for结构2、for...else结构三、break和continue的生效范......
  • 装饰器基础二装饰类的装饰器
    类可以做装饰器,也可以被装饰器装饰,被装饰器装饰,那这个装饰器可以接收一个类,返回一个类1、装饰类的装饰器-输入类返回类defadd_str(cls):def__str__(self):......
  • 创建springboot+maven项目
    参考:https://www.cnblogs.com/ygfsy/p/13675122.html 目录结构-->在项目下创建一个Control包,用于返回给前端的接口   测试接口是否通 ......
  • 查找java位置
    方法一:whichjava此方法是无法定位到java的安装路径的,只能定位到执行路径。 方法二:echo$JAVA_HOME 使用echo$JAVA_HOME命令可以定位到java安装路径,但前......