当前位置:网站首页>单例模式你会几种写法?
单例模式你会几种写法?
2022-08-02 20:52:00 【马小屑】
这阵子在刷Spring的书籍。在看Spring的时候又经常会看到“单例”,“工厂”这些字样。
所以,就先来说说单例和工厂设计模式啦,这两种模式也是很常见的,我看很多面经都会遇到这两种模式~
本文主要讲解单例设计模式,如果有错的地方希望能多多包涵,并不吝在评论区指正!
一、单例模式概述
单例模式定义很简单:一个类中能创建一个实例,所以称之为单例!
那我们什么时候会用到单例模式呢??
那我们想想既然一个类中只能创建一个实例了,那么可以说这是跟类的状态与对象无关的了。
频繁创建对象、管理对象是一件耗费资源的事,我们只需要创建一个对象来用就足够了!
学过Java Web的同学可能就知道:
Servlet是单例的
Struts2是多例的
SpringMVC是单例的
那既然多例是频繁创建对象、需要管理对象的,那Struts2为什么要多例呢??
主要由于设计层面上的问题,Struts2是基于Filter拦截类的,ognl引擎对变量是注入的。所以它要设计成多例的~
能使用一个对象来做就不用实例化多个对象!这就能减少我们空间和内存的开销~
那有可能有的人又会想了:我们使用静态类.doSomething()
和使用单例对象调用方法的效果是一样的啊。
没错,效果就是一样的。使用
静态类.doSomething()
体现的是基于对象,而使用单例设计模式体现的是面向对象。
二、编写单例模式的代码
编写单例模式的代码其实很简单,就分了三步:
将构造函数私有化
在类的内部创建实例
提供获取唯一实例的方法
2.1饿汉式
根据上面的步骤,我们就可以轻松完成创建单例对象了。
public class Java3y {
// 1.将构造函数私有化,不可以通过new的方式来创建对象
private Java3y(){}
// 2.在类的内部创建自行实例
private static Java3y java3y = new Java3y();
// 3.提供获取唯一实例的方法
public static Student getJava3y() {
return java3y;
}
}
这种代码我们称之为:“饿汉式”:
一上来就创建对象了,如果该实例从始至终都没被使用过,则会造成内存浪费。
2.2简单懒汉式
既然说一上来就创建对象,如果没有用过会造成内存浪费:
那么我们就设计用到的时候再创建对象
public class Java3y {
// 1.将构造函数私有化,不可以通过new的方式来创建对象
private Java3y(){}
// 2.1先不创建对象,等用到的时候再创建
private static Java3y java3y = null;
// 2.1调用到这个方法了,证明是要被用到的了
public static Java3y getJava3y() {
// 3. 如果这个对象引用为null,我们就创建并返回出去
if (java3y == null) {
java3y = new Java3y();
}
return java3y;
}
}
上面的代码行不行??在单线程环境下是行的,在多线程环境下就不行了!
要解决也很简单,我们只要加锁就行了:
2.3双重检测机制(DCL)懒汉式
上面那种直接在方法上加锁的方式其实不够好,因为在方法上加了内置锁在多线程环境下性能会比较低下,所以我们可以将锁的范围缩小。
public class Java3y {
private Java3y() {
}
private static Java3y java3y = null;
public static Java3y getJava3y() {
if (java3y == null) {
// 将锁的范围缩小,提高性能
synchronized (Java3y.class) {
java3y = new Java3y();
}
}
return java3y;
}
}
那上面的代码可行吗??不行,因为虽然加了锁,但还是有可能创建出两个对象出来的:
线程A和线程B同时调用
getJava3y()
方法,他们同时判断java==null
,得出的结果都是为null,所以进入了if代码块了此时线程A得到CPU的控制权-->进入同步代码块-->创建对象-->返回对象
线程A完成了以后,此时线程B得到了CPU的控制权。同样是-->进入同步代码块-->创建对象-->返回对象
很明显的是:Java3y类返回了不止一个实例!所以上面的代码是不行的!
有的同学可能觉得我瞎吹比,明明加锁了还不行?我们来测试一下:
public class TestDemo {
public static void main(String[] args) {
// 线程A
new Thread(() -> {
// 创建单例对象
Java3y java3y = Java3y.getJava3y();
System.out.println(java3y);
}).start();
// 线程B
new Thread(() -> {
// 创建单例对象
Java3y java3y = Java3y.getJava3y();
System.out.println(java3y);
}).start();
// 线程C
new Thread(() -> {
// 创建单例对象
Java3y java3y = Java3y.getJava3y();
System.out.println(java3y);
}).start();
}
}
可以看到,打印出的对象不单单只有一个的!
厉害的程序员又想到了:进入同步代码块时再判断一下对象是否存在就稳了吧!
所以,有了下面的代码
public class Java3y {
private Java3y() {
}
private static Java3y java3y = null;
public static Java3y getJava3y() {
if (java3y == null) {
// 将锁的范围缩小,提高性能
synchronized (Java3y.class) {
// 再判断一次是否为null
if (java3y == null) {
java3y = new Java3y();
}
}
}
return java3y;
}
}
其实还不稳!这里会有重排序的问题:
本来想测试重排序问题的效果的,一直没测试出来~~~有相关测试代码的希望可以告诉我怎么能测出来….
要解决也十分简单,加上我们的volatile关键字就可以了,volatile有内存屏障的功能!
所以说,完整的DCL代码是这样子的:
public class Java3y {
private Java3y() {
}
private static volatile Java3y java3y = null;
public static Java3y getJava3y() {
if (java3y == null) {
// 将锁的范围缩小,提高性能
synchronized (Java3y.class) {
// 再判断一次是否为null
if (java3y == null) {
java3y = new Java3y();
}
}
}
return java3y;
}
}
再说明:
2.4静态内部类懒汉式
还可以使用静态内部类这种巧妙的方式来实现单例模式!它的原理是这样的:
当任何一个线程第一次调用
getInstance()
时,都会使SingletonHolder被加载和被初始化,此时静态初始化器将执行Singleton的初始化操作。(被调用时才进行初始化!)初始化静态数据时,Java提供了的线程安全性保证。(所以不需要任何的同步)
public class Java3y {
private Java3y() {
}
// 使用内部类的方式来实现懒加载
private static class LazyHolder {
// 创建单例对象
private static final Java3y INSTANCE = new Java3y();
}
// 获取对象
public static final Java3y getInstance() {
return LazyHolder.INSTANCE;
}
}
静态内部类这种方式是非常推荐使用的!很多人没接触过单例模式的都不知道有这种写法,这种写法很优化也高效!
2.5枚举方式实现
使用枚举就非常简单了:
public enum Java3y3y {
JAVA_3_Y_3_Y,
}
那这种有啥好处??枚举的方式实现:
简单,直接写就行了
防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候(安全)!
这种也较为推荐使用!
三、总结
总的来说单例模式写法有5种:
饿汉式
简单懒汉式(在方法加锁)
DCL双重检测加锁(进阶懒汉式)
静态内部类实现懒汉式(最推荐写法)
枚举方式(最安全、简洁写法)
边栏推荐
猜你喜欢
【3D视觉】深度摄像头与3D重建
go——内存分配机制
SublimeText3 安装、配置项、包管理、常用必备插件、常用快捷键以及修改
千人优学 | GBase 8s数据库2022年6月大学生专场实训圆满结束
vscode如何能将输出从OUTPUT改为TERMINAL或者DebugConsole
【3D视觉】realsense D435三维重建
汉源高科千兆4光4电工业级网管型智能环网冗余以太网交换机防浪涌防雷导轨式安装
.NET performance optimization - you should set initial size for collection types
Bee 事务注解 @Tran 使用实例
Swin Transformer 论文精读,并解析其模型结构
随机推荐
How to use windbg check c # a thread stack size?
PLC工作原理动画
2018HBCPC个人题解
汇编语言中b和bl关键字的区别
C# Barrier类
.NET performance optimization - you should set initial size for collection types
解道9-编程技术6
信息学奥赛一本通(1257:Knight Moves)
golang 刷leetcode:统计打字方案数
如何理解 swing 是非线程安全 (原创)
李沐动手学深度学习V2-bert预训练数据集和代码实现
解道8-编程技术5
Electrical diagram of power supply system
快速构建电脑软件系统 、超好用经典的网页推荐汇总
go——垃圾回收机制(GC)
引用类型 ,值类型 ,小坑。
js how to get the browser zoom ratio
Use the TCP protocol, we won't lost package?
golang 刷leetcode:从栈中取出 K 个硬币的最大面值和
golang刷leetcode:到达角落需要移除障碍物的最小数目