关键概念

优点:
有些实例,全局只需要一个就够了,使用单例模式就可以避免一个全局使用的类,频繁的创建与销毁,耗费系统资源。
关键概念
- 一个私有构造函数(确保只能单例类自己创建实例):
单例类通常会将其构造函数设为私有,以防止外部代码直接实例化对象。
- 一个私有静态变量(确保只有一个实例)
单例类通常包含一个私有的静态变量,用于保存该类的唯一实例。
- 一个公有静态函数(给使用者提供调用方法)
单例类提供一个公共的静态方法,用于获取该类的实例。如果实例不存在,则在该方法内部创建实例并返回。
简单来说就是,单例类的构造方法不让其他人修改和使用;并且单例类自己只创建一个实例,这个实例,其他人也无法修改和直接使用;然后单例类提供一个调用方法,想用这个实例,只能调用。这样就确保了全局只创建了一个
实例。
单例模式的6种实现
单例模式6种实现及各实现的优缺点
1、懒汉式(线程不安全)
private static final Singleton instance = new Singleton();
// 私有构造函数 private Singleton() {}
// 公共静态方法,返回实例 public static Singleton getInstance() {
return instance;
} }
说明:
先不创建实例,当第一次被调用时,再创建实例,所以被称为懒汉式。
优点:
延迟了实例化,如果不需要使用该类,就不会被实例化,只有在需要时才创建实例,避免了资源浪费。
缺点:
线程不安全,多线程环境下,如果多个线程同时进入了 if (uniqueInstance == null),若此时还未实例化,也就是 uniqueInstance == null,那么就会有多个线程执行 uniqueInstance = new Singleton();,就会实例化多个实例;
2、饿汉式(线程安全)
实现:
public class Singleton { // 类加载时就创建实例
private static final Singleton instance = new Singleton();
// 私有构造函数 private Singleton() {}
// 公共静态方法,返回实例 public static Singleton getInstance() {
return instance;
} }
说明:
先不管需不需要使用这个实例,直接先实例化好实例(饿死⻤一样,所以称为饿汉式),然后当需要使用的时候,直接调方法就可以使用了
优点:
提起实例化好了一个实例,避免了线程不安全问题的出现,
会使操作系统的资源浪费。
3、懒汉式(线程安全)
实现:
public class Singleton { private static Singleton uniqueInstance;
private static singleton() {} private static synchronized Sing leton getUinqueInstance() {
if (uniqueInstance == null) { uniqueInstance = new Singleton();
} return uniqueInstance;
} }
说明:
实现和线程不安全的懒汉式几乎一样,唯一不同的点是,在get方法上加了一把锁。如此一来,多个线程访问,每次只有拿到锁的的线程能够进入该方法,避免了多线程不安全问题的出现。
优点:
延迟实例化,节约了资源,并且是线程安全的。
缺点:
虽然解决了线程安全问题,但是性能降低了。因为,即使实例已经实例化了,既后续不会再出现线程安全问题了,但是锁还在,每次还是只能拿到锁的线程进入该方法使线程阻塞,等待时间过长。
4、双重检查锁实现(线程安全)
实现:
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();
} }
}
说明:
双重检查锁相当于是改进了线程安全的懒汉式。线程安全的懒汉式的缺点是性能降低了,造成的原因是因为即使实例已经实例化,依然每次都会有锁。
而现在,我们将锁的位置变了,并且多加了一个检查。也就是,先判断实例是否已经存在,若已经存在了,则不会执行判断方法内的有锁方法了。而如果,还没有实例化的时候,多个线程进去了,也没有事,因为里面的方法有
锁,只会让一个线程进入最内层方法并实例化实例。如此一来,最多最多,也就是第一次实例化的时候,会有线程阻塞的情况,后续便不会再有线程阻塞的问题。
为什么使用 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 关键字,对性能也有一些影响。
5、静态内部类实现(线程安全)
实现:
// 静态内部类持有实例 private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
// 公共静态方法,返回实例 public static Singleton getInstance() {
return SingletonHolder.instance;
} }
说明:
首先,当外部类 Singleton 被加载时,静态内部类 SingletonHolder 并没有被加载进内存。当调用 getUniqueInstance() 方法时,会运行 return SingletonHolder.INSTANCE;
触发了 SingletonHolder.INSTANCE,此时静态内部类 SingletonHolder 才会被加载进内存,并且初始化 INSTANCE 实例,而且 JVM 会确保 INSTANCE 只被实例化一次。
优点:
延迟实例化,节约了资源,且线程安全,性能也提高了。
6、枚举类实现(线程安全)
实现:
public enum Singleton { INSTANCE;
// 可以添加其他方法和属性 public void doSomething() {
// 实现 }
}
说明:
默认枚举实例的创建就是线程安全的,且在任何情况下都是单例。
优点:
写法简单,线程安全,天然防止反射和反序列化调用。
防止反序列化
序列化:
全站分卷索引 · 230 卷
- 1代码随想录知识星球精华(最强八股文)第五版(计算机基础篇)
- 2DNS
- 3DNS解析过程
- 4递归查询和迭代查询
- 5HTTP特性与简述
- 6HTTP版本演变
- 7HTTP/2
- 8HTTP1.1协议的性能问题
- 9兼容HTTP1.1
- 10二进制帧
- 11并发传输
- 12服务器主动推送资源
- 13总结
- 14HTTP/3
- 15HTTP/2的缺点
- 16QUIC协议的特点
- 17HTTP/3协议
- 18HTTP缓存
- 19协商缓存
- 20HTTPS
- 21HTTPS的特点
- 22非对称加密
- 23摘要算法+数字签名
- 24数字证书
- 25证书信任链
- 26HTTPS是怎么建立连接的
- 27TCP与UDP头部格式
- 28TCP
- 29列举TCP选项
- 30TCP三次握手
- 31为什么需要三次握手?
- 32什么是半连接队列
- 33什么是SYN攻击
- 34为什么挥手需要四次
- 35TIME_WAIT 过多有什么危害?
- 36TCP的滑动窗口
- 374. 程序是如何表示发送方的四个部分的呢?
- 386. 接收窗口和发送窗口的大小是相等的吗?
- 391. 慢启动
- 403. 拥塞发生
- 414. 快速恢复
- 42IP基础
- 43IP地址
- 44在浏览器中输入URL并按下回车之后会发生什么
- 45TCP 抓包实践
- 46TCP连接与断开优化
- 47TCP内核参数
- 48主动方的优化
- 49被动方的优化
- 50Linux是如何收发网络包的
- 51收发流程
- 52面试题
- 53TCP/IP四层网络模型
- 54五层网络体系结构
- 55HTTP响应报文
- 56HTTP常见字段
- 57什么是TCP粘包问题?如何解决TCP粘包问题?
- 58UDP会不会产生粘包问题呢?
- 59请求报文
- 60时间消耗
- 61HTTP缓存有几种
- 62HTTP1.0和HTTP1.1的区别?
- 63HTPP1.1和HTTP1.0有什么区别
- 64HTTP/3
- 65HTTPS是如何建立连接的
- 66DNS是什么
- 67递归查询和迭代查询
- 68TCP与UDP的区别
- 69TCP和UDP分别在什么场景使用?
- 70三次握手的过程
- 71Keep-Alive的优缺点
- 72CDN的工作流程
- 73Session的工作原理
- 74TCP重传机制是怎么实现的?
- 75什么是SS0
- 76HTTP优化方案
- 77浏览器地址栏输入URL回车后涉及到的流程
- 781.5 浏览器发送HTTP/HTTPS请求到web服务器
- 79SYN攻击
- 80操作系统
- 81操作系统的功能
- 82进程和线程
- 83线程基础
- 84进程调度
- 85进程调度方式
- 86进程通信
- 87消息传递(消息队列)
- 88信号量
- 89信号
- 90线程通信
- 91多线程
- 92进程通信
- 93消息队列
- 94信号
- 95守护进程
- 96互斥与同步
- 974. 互斥锁解锁
- 983. 处理方法
- 99读写锁
- 1002. 销毁读写锁
- 1014. 写锁定
- 1021. 创建条件变量
- 1034. 阻塞线程
- 104信号量
- 1054. V操作
- 1062. 读者-写者问题
- 107存储系统
- 108虚拟内存
- 109内存分段
- 110内存分页
- 111虚拟内存
- 112文件系统
- 1135. 文件访问
- 114目录
- 1154. 目录操作
- 1163. 目录的实现
- 1177. 虚拟文件系统
- 1184. 文件系统性能
- 119磁盘编址
- 120三层抽象
- 121外设管理
- 122Linux
- 123select/poll/epoll的区别和联系
- 124Linux 内存管理
- 125Linux虚拟内存
- 126Linux 信号
- 127Linux设计
- 1283. 各种IO模型的比较
- 129Linux网络
- 130Linux中的软链接和硬链接有什么区别?
- 131什么是中断和异常
- 132信号和信号量有什么区别
- 133Linux中如何查看一个进程
- 134进程、线程的区别?
- 135进程之间的通信方式
- 136如何解除死锁
- 137分段和分页的区别
- 138IO多路复用
- 139MySQL表连接
- 140过滤条件
- 141执行一次update语句,发生了什么
- 142用户账号存储
- 143用户账号删除
- 144索引
- 145索引有哪些类别
- 1462. 按照存储分类
- 1475. 按照字段个数分类
- 148索引区分度
- 149索引什么时候会失效
- 150事务的四大特性ACID
- 151并行事务会出现什么问题
- 152幻读是如何解决的
- 153事务隔离的实现
- 154读已提交是怎么实现的
- 155锁
- 1561、全局锁
- 1573、行级锁
- 158锁的划分
- 1592.、程序员角度
- 160Innodb使用表锁还是行锁
- 161什么是binlog
- 162执行引擎
- 163MySQL常见问题
- 1642. 事务
- 1653. 脏页
- 1664. 表结构
- 1676. 查询效率
- 168MySQL分库分表
- 169垂直分表
- 170水平分库
- 171分库分表的问题
- 172数据库排序思路
- 173MySQL主从复制
- 1745. 主从复制模式
- 175面试题
- 176执行一条select语句,发生了什么?
- 177你对关系范式有哪些了解
- 178数据库
- 179Redis基础
- 180Redis单线程为什么还这么快
- 181Redis底层数据结构
- 182Redis持久化
- 183AOF日志
- 184RDB快照
- 185Redis功能
- 1862. 哨兵模式
- 1873. 切片集群
- 188Redis哨兵机制是什么
- 189什么是集群脑裂
- 190过期删除策略有哪些
- 191Redis的LRU算法和LFU算法有什么区别
- 192什么是缓存击穿
- 193如何保证删除缓存操作一定能成功?
- 194为什么Redis是单线程
- 195Redis底层使用了什么数据结构
- 196Redis实现分布式锁
- 197缓存中的热点数据和冷数据是什么
- 198缓存雪崩、击穿、穿透和解决办法【常问】
- 199Redis过期删除策略有哪些
- 200关键概念
- 201单例模式的应用场景
- 202工厂模式
- 203实际应用
- 204抽象工厂模式
- 205概念
- 206应用
- 207实现步骤
- 208主要角色
- 209代理模式应用
- 210策略模式
- 211示例
- 212装饰模式
- 213主要角色
- 21423种设计模式的概念
- 215创建型模式
- 216行为型模式
- 217设计原则
- 218迪米特法则
- 219你在实际开发中用过哪些设计模式
- 220依赖倒置
- 221代理模式和适配器模式有什么区别
- 222固定长度编码
- 223冯诺依曼模型
- 224总线
- 225输入/输出设备
- 226编译系统如何工作
- 227处理器解释指令
- 228场景题 & 系统设计
- 229如何设计一个本地缓存
- 230如何设计一个RPC框架