当前位置:网站首页>6种单例模式的实现方式
6种单例模式的实现方式
2022-07-02 07:13:00 【LHT-2787】
一、单例模式的定义
定义: 确保一个类只有一个实例,并提供该实例的全局访问点。
这样做的好处是:有些实例,全局只需要一个就够了,使用单例模式就可以避免一个全局使用的类,频繁的创建与销毁,耗费系统资源。
二、单例模式的设计要素
一个私有构造函数 (确保只能单例类自己创建实例)
一个私有静态变量 (确保只有一个实例)
一个公有静态函数 (给使用者提供调用方法)
简单来说就是,单例类的构造方法不让其他人修改和使用;并且单例类自己只创建一个实例,这个实例,其他人也无法修改和直接使用;然后单例类提供一个调用方法,想用这个实例,只能调用。这样就确保了全局只创建了一次实例。
三、单例模式的6种实现及各实现的优缺点
(一)懒汉式(线程不安全)
实现:
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 只被实例化一次。
优点: 延迟实例化,节约了资源;且线程安全;性能也提高了。
(六)枚举类实现(线程安全)
实现:
public enum Singleton {
INSTANCE;
//添加自己需要的操作
public void doSomeThing() {
}
}
说明: 默认枚举实例的创建就是线程安全的,且在任何情况下都是单例。
优点: 写法简单,线程安全,天然防止反射和反序列化调用。
防止反序列化
序列化:把java对象转换为字节序列的过程;
反序列化: 通过这些字节序列在内存中新建java对象的过程;
说明: 反序列化 将一个单例实例对象写到磁盘再读回来,从而获得了一个新的实例。
我们要防止反序列化,避免得到多个实例。
枚举类天然防止反序列化。
其他单例模式 可以通过 重写 readResolve() 方法,从而防止反序列化,使实例唯一重写 readResolve() :
1
2
3
private Object readResolve() throws ObjectStreamException{
return singleton;
}
四、单例模式的应用场景
应用场景举例:
网站计数器。
应用程序的日志应用。
Web项目中的配置对象的读取。
数据库连接池。
多线程池。
…
使用场景总结:
频繁实例化然后又销毁的对象,使用单例模式可以提高性能。
经常使用的对象,但实例化时耗费时间或者资源多,如数据库连接池,使用单例模式,可以提高性能,降低资源损坏。
使用线程池之类的控制资源时,使用单例模式,可以方便资源之间的通信。
摘录自:https://www.nowcoder.com/test/question/done?tid=57875117&qid=304933#summary
边栏推荐
- Rapid prototyping
- session-cookie与token
- PCL 从一个点云中提取一个子集
- stm32和電機開發(上比特系統)
- Session cookies and tokens
- JS settimeout() and interview questions
- The nanny level tutorial of flutter environment configuration makes the doctor green to the end
- nodejs+express+mysql简单博客搭建
- 记录 AttributeError: ‘NoneType‘ object has no attribute ‘nextcall‘
- 01安装虚拟机
猜你喜欢
shell编程01_Shell基础
MySQL数据库远程访问权限设置
Determine whether there are duplicate elements in the array
SPSS做Shapiro-Wilk正态分析
Analysis of hot spots in AI technology industry
"Talking about podcasts" vol.352 the age of children: breaking the inner scroll, what can we do before high school?
"Matching" is true love, a new attitude for young people to make friends
《MySQL 8 DBA基础教程》简介
Read H264 parameters from mediarecord recording
13. Semaphore critical zone protection
随机推荐
[Lua] summary of common knowledge points (including common interview sites)
Considerations for Apache deploying static web page projects
14.信号量的代码实现
SQOOP 1.4.6 INSTALL
01安装虚拟机
Merge ordered sequence
JSP webshell免杀——webshell免杀
Sus system availability scale
2.hacking-lab脚本关[详细writeup]
UVM learning - build a simple UVM verification platform
session-cookie与token
SUS系统可用性量表
js setTimeout()与面试题
SPSS做Shapiro-Wilk正态分析
Operator-1 first acquaintance with operator
12.进程同步与信号量
Flink calculates topn hot list in real time
Delivery mode design of Spartacus UI of SAP e-commerce cloud
数据库字典Navicat自动生成版本
flume 190 INSTALL