07 创建型模式-单例模式

news2024/12/24 2:51:29
1.单例模式介绍

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,此模式保证
某个类在运行期间,只有一个实例对外提供服务,而这个类被称为单例类。

2.使用单例模式要做的两件事
  1. 保证一个类只有一个实例
  2. 为该实例提供一个全局访问节点
3. 单例模式结构

在这里插入图片描述

4.1 单例模式之饿汉式:

在类加载期间初始化静态实例,保证 instance 实例的创建是线程安全的 ( 实例在
类加载时实例化,有JVM保证线程安全).
特点: 不支持延迟加载实例(懒加载) , 此中方式类加载比较慢,但是获取实例对象
比较快。

问题: 该对象足够大的话,而一直没有使用就会造成内存的浪费。

public class Singleton_01 {

	//1. 私有构造方法
	private Singleton_01(){}

	//2. 在本类中创建私有静态的全局对象
	private static Singleton_01 instance = new Singleton_01();

	//3. 提供一个全局访问点,供外部获取单例对象
	public static Singleton_01 getInstance(){
		return instance;
	}

}
4.2懒汉式(线程不安全)

此种方式的单例实现了懒加载,只有调用getInstance方法时 才创建对象.但是如
果是多线程情况,会出现线程安全问题.

public class Singleton_02 {

	//1. 私有构造方法
	private Singleton_02(){}

	//2. 在本类中创建私有静态的全局对象
	private static Singleton_02 instance;

	//3. 通过判断对象是否被初始化,来选择是否创建对象
	public static Singleton_02 getInstance(){
		if(instance == null){
			instance = new Singleton_02();
		}
		return instance;
	}

}

注意:
假设在单例类被实例化之前,有两个线程同时在获取单例对象,线程A在执
行完if (instance == null) 后,线程调度机制将 CPU 资源分配给线程B,此
时线程B在执行 if (instance == null)时也发现单例类还没有被实例化,这样
就会导致单例类被实例化两次。为了防止这种情况发生,需要对
getInstance() 方法同步处理。改进后的懒汉模式.

懒汉式(线程安全)

原理: 使用同步锁 synchronized 锁住 创建单例的方法 ,防止多个线程同时调
用,从而避免造成单例被多次创建。

public class Singleton_03 {

	//1. 私有构造方法
	private Singleton_03(){}

	//2. 在本类中创建私有静态的全局对象
	private static Singleton_03 instance;

	//3. 通过添加synchronize,保证多线程模式下的单例对象的唯一性
	public static synchronized Singleton_03 getInstance(){
		if(instance == null){
			instance = new Singleton_03();
		}
		return instance;
	}

}

缺点
懒汉式的缺点也很明显,我们给 getInstance() 这个方法加了一把大锁
(synchronzed),导致这个函数的并发度很低。量化一下的话,并发度
是 1,也就相当于串行操作了。而这个函数是在单例使用期间,一直会被调
用。如果这个单例类偶尔会被用到,那这种实现方式还可以接受。但是,
如果频繁地用到,那频繁加锁、释放锁及并发度低等问题,会导致性能瓶
颈,这种实现方式就不可取了。

双重校验

饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。那我们再来看一
种既支持延迟加载、又支持高并发的单例实现方式,也就是双重检测实现方
式。

实现步骤:

  1. 在声明变量时使用了 volatile 关键字,其作用有两个:
    保证变量的可见性:当一个被volatile关键字修饰的变量被一个线程修改的时
    候,其他线程可以立刻得到修改之后的结果。
    屏蔽指令重排序:指令重排序是编译器和处理器为了高效对程序进行优化的手
    段,它只能保证程序执行的结果时正确的,但 是无法保证程序的操作顺序与代
    码顺序一致。这在单线程中不会构成问题,但是在多线程中就会出现问题。
  2. 将同步方法改为同步代码块. 在同步代码块中使用二次检查,以保证其不被
    重复实例化 同时在调用getInstance()方法时不进行同步锁,效率高。
public class Singleton_04 {

	//1. 私有构造方法
	private Singleton_04(){}

	//2. 使用 volatile保证变量的可见性
	private volatile static Singleton_04 instance = null;

	//3. 对外提供静态方法获取对象
	public static Singleton_04 getInstance(){
		//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
		if(instance == null){
			synchronized (Singleton_04.class){
				//抢到锁之后再次进行判断是否为null
				if(instance == null){
					instance = new Singleton_04();
				}
			}
		}
		return instance;
	}

}
静态内部类

原理 根据静态内部类 的特性(外部类的加载不影响内部类),同时解决了按
需加载、线程安全的问题,同时实现简洁。

  1. 在静态内部类里创建单例,在装载该内部类时才会去创建单例
  2. 线程安全:类是由 JVM 加载,而 JVM 只会加载1遍,保证只有1个单例
public class Singleton_05 {

	private static class SingletonHandler{
		private static Singleton_05 instance = new Singleton_05();
	}

	private Singleton_05(){}

	public static Singleton_05 getInstance(){
		return SingletonHandler.instance;
	}

}
反射对于单例的破坏

反射技术过于强大,它可以通过 setAccessible() 来修改构造器,字段,方法
的可见性。单例模式的构造方法是私有的,如果将其可见性设为 public ,那么
将无法控制对象的创建。

public class Test_Reflect {

	public static void main(String[] args) {
		try {
			//反射中,欲获取一个类或者调用某个类的方法,首先要获取到该类的Class 对象。
			Class<Singleton_05> clazz = Singleton_05.class;
			//getDeclaredXxx: 不受权限控制的获取类的成员.
			Constructor c = clazz.getDeclaredConstructor(null);
			//设置为true,就可以对类中的私有成员进行操作了
			c.setAccessible(true);
			Object instance1 = c.newInstance();
			Object instance2 = c.newInstance();
			System.out.println(instance1 == instance2);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

解决方法之一: 在单例类的构造方法中 添加判断 instance != null 时,直接抛
出异常

public class Singleton_05 {
	private static class SingletonHandler{
		private static Singleton_05 instance = new Singleton_05();
	}
	private Singleton_05(){
		if(SingletonHandler.instance != null){
			throw new RuntimeException("不允许非法访问!");
		}
	}
	public static Singleton_05 getInstance(){
		return SingletonHandler.instance;
	}
}

上面的这种方式使代码简洁性遭到破坏,设计不够优雅.

序列化对于单例的破坏
public class Test_Serializable {
	@Test
	public void test() throws Exception{
		//序列化对象输出流
		ObjectOutputStream oos = new ObjectOutputStream(newFileOutputStream("tempFile.obj"));
		oos.writeObject(Singleton.getInstance());
		//序列化对象输入流
		File file = new File("tempFile.obj");
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
		Singleton Singleton = (Singleton) ois.readObject();
		System.out.println(Singleton);
		System.out.println(Singleton.getInstance());
		//判断是否是同一个对象
		System.out.println(Singleton.getInstance() == Singleton);//false
	}
}

class Singleton implements Serializable{
	private volatile static Singleton singleton;

	private Singleton() {}

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

输出结构为false,说明:
通过对Singleton的序列化与反序列化得到的对象是一个新的对象,这就 破坏了
Singleton的单例性 。

解决方案:

/**
* 解决方案:只要在Singleton类中定义readResolve就可以解决该问题
* 程序会判断是否有readResolve方法,如果存在就在执行该方法,如果不存在-
-就创建一个对象
*/
private Object readResolve() {
	return singleton;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
readOrdinaryObject方法的代码片段

private Object readOrdinaryObject(boolean unshared)
throws IOException
{
//此处省略部分代码
Object obj;
try {
//通过反射创建的这个obj对象,就是本方法要返回的对象,也
可以暂时理解为是ObjectInputStream的readObject返回的对象。
//isInstantiable:如果一个serializable的类可以在运行
时被实例化,那么该方法就返回true
//desc.newInstance:该方法通过反射的方式调用无参构造方
法新建一个对象。
obj = desc.isInstantiable() ? desc.newInstance()
: null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
return obj;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5 枚举(推荐方式)

在这里插入图片描述

public enum Singleton_06{

	INSTANCE;
	private Object data;

	public Object getData() {
		return data;
	}

	public void setData(Object data) {
		this.data = data;
	}

	public static Singleton_06 getInstance(){
		return INSTANCE;
	}
}

在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1106307.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言实现:有 5 个人坐在一起,问第 5 个人多少岁?

完整代码&#xff1a; /*有 5 个人坐在一起&#xff0c;问第 5 个人多少岁?他说比第 4 个人大两岁。间第 4 个人岁 数&#xff0c;他说比第 3 个人大两岁。问第 3 个人&#xff0c;又说比第 2 个人大两岁。问第 2 个人&#xff0c; 说比第 1 个人大两岁。最后问第 1 个人&…

Linux内核驱动开发:技能、应用和挑战

Linux内核驱动开发&#xff1a;技能、应用和挑战 Linux内核驱动开发所需技能包括C语言和汇编语言编程、Linux系统编程接口、内核原理和机制、设备驱动开发和调试技术等。同时&#xff0c;熟悉常用的开发环境和工具如GNU工具链、make、gcc、gdb等也是必要的。 Linux内核驱动开…

在华为和比亚迪干了5年测试,月薪25K,熬夜总结出来的划水经验.....

先简单交代一下背景吧&#xff0c;某不知名 985 的本硕&#xff0c;17 年毕业加入华为&#xff0c;之后跳槽到了比亚迪&#xff0c;一直从事软件测试的工作。之前没有实习经历&#xff0c;算是5年的工作经验吧。 这5年之间完成了一次晋升&#xff0c;换了一家公司&#xff0c;…

scada组态软件和硬件结构的主要功能

scada在工厂自动化领域有许多应用。在行业内&#xff0c;所有现场仪表都单独连接到接线盒。多对电缆将从接线盒连接到控制室。 在scada中&#xff0c;电缆被接收在编组柜中。这里&#xff0c;电缆分为模拟输入&#xff08;AI&#xff09;、模拟输出&#xff08;AO&#xff09;…

3D视觉基础

3D 视觉领域是计算机视觉的一个重要分支&#xff0c;旨在模拟和分析三维空间中的物体、场景和运动。它结合了图像处理、计算几何学、机器学习和深度学习等技术&#xff0c;以从二维图像或传感器数据中重建三维信息。本文主要介绍 3D 视觉中的一些基础概念。 目录 一. 三维坐标系…

Datawhale学习笔记AI +新能源:电动汽车充电站充电量预测

赛题介绍 建立站点充电量预测模型&#xff0c;根据充电站的相关信息和历史电量数据&#xff0c;准确预测未来某段时间内充电站的充电量需求。 在赛题数据中&#xff0c;我们提供了电动汽车充电站的场站编号、位置信息、历史电量等基本信息。我们鼓励参赛选手在已有数据的基础上…

【C++初阶】引用内联函数auto关键字范围for循环nullptr

个人主页还有更多系列专栏&#xff1a;小白不是程序媛 我的小仓库&#xff1a;Gitee C系列专栏&#xff1a;C头疼记 目录 前言 引用 概念 引用的特点 常引用 引用的使用场景 做参数 做返回值 引用和指针的区别 引用和指针的不同点&#xff1a; 内联函数 内联函数…

【LeetCode刷题(数据结构与算法)】:二叉搜索树的范围和

一、什么是二叉搜索树 二叉搜索树&#xff08;BST&#xff0c;Binary Search Tree&#xff09;&#xff0c;也称二叉排序树或二叉查找树 二叉搜索树&#xff1a;一棵二叉树&#xff0c;可以为空&#xff1b;如果不为空&#xff0c;满足以下性质 非空左子树的所有键值小于其根结…

Typora使用教程

相关介绍 Typora是一款所写即所得的Markdown编辑器&#xff0c;支持跨平台Window,Linux和Mac都可以。 相关链接 Typora安装包历史版本地址&#xff1a;官网历史版本地址 问题及知识点汇总 关于图片的问题-设置图像自动复制到自己指定文件夹下 问题描述 在写笔记的过程中&am…

Write-Ahead Log(PostgreSQL 14 Internals翻译版)

日志 如果发生停电、操作系统错误或数据库服务器崩溃等故障&#xff0c;RAM中的所有内容都将丢失&#xff1b;只有写入磁盘的数据才会被保留。要在故障后启动服务器&#xff0c;必须恢复数据一致性。如果磁盘本身已损坏&#xff0c;则必须通过备份恢复来解决相同的问题。 理论…

C++ 类和对象(上)------超详细解析,小白必看系列

目录 一、前言 二、面向过程和面向对象初步认识 三、类的引入 三、类的定义 四、类的访问限定符及封装 &#x1f4a6;访问限定符 &#xff08;重点&#xff01;&#xff01;&#xff01;&#xff01;&#xff09; &#x1f4a6;封装 五、类的作用域 六、类的实例化 …

算法--排序算法效率比较

《算法设计与分析》课程实验报告 &#xff08; 实验一&#xff09; 实验名称&#xff1a;排序算法效率比较 实验地点&#xff1a; 所使用的开发工具及环境&#xff1a; PC机&#xff0c;DEV 一、实验目的&#xff1a; 比较至少 4 种排序&#xff08;从小到大排&#xff09…

从十月稻田,看大米为何能卖出200亿市值?

国无农不稳&#xff0c;民无粮不安。新时代的农村农民&#xff0c;需要现代化的农业作依托&#xff0c;而在农业现代化的过程中&#xff0c;品牌化、数字化成为至关重要的一环。 金秋十月&#xff0c;从南到北&#xff0c;从东到西&#xff0c;中国农村的每一块土地都洋溢着丰…

【运筹优化】运筹学导论:线性规划导论

文章目录 一、原形范例&#xff08;Wyndor Glass 公司&#xff09;1.1 线性规划模型构建1.2 图解法1.3 结论 二、线性规划模型2.1 线性规划模型的标准形式2.2 其他形式2.3 模型解的术语 三、有关线性规划的假设3.1 比例性3.2 可加性3.3 可分割性3.4 确定性 四、补充例子4.1 放射…

ant的basedir内置属性

basedir是ant的内置属性&#xff0c;代表项目基础路径的绝对路径。它的默认值等于build文件的父目录。当然&#xff0c;可以通过project 的basedir 属性、或者单独的basedir 属性来覆盖。 例如&#xff0c;在工程的根目录下有个build.xml文件&#xff1a; build.xml文件的内…

java springboot 实现 对象或对象数组 转为 前端可解析的JSON字符串格式

这是我的接口代码 逻辑就是 通过 IBookService对象中的 list 函数获取数据列表 然后定义 state(响应状态) message(提示信息) 最后将这三个信息 做成对象返回给前端 接口访问结果 但是这种对象数组对存储空间消耗肯定是比字符串大很多的 所以 我们来尝试 将json数组转成字符…

JOSEF约瑟 漏电继电器 JHOK-ZBG1 φ25mm AC220V 0.1A/0.1S 分体式

系列型号 JHOK-ZBG1 φ25mm漏电&#xff08;剩余&#xff09;继电器 JHOK-ZBG2 φ25mm漏电&#xff08;剩余&#xff09;继电器 JHOK-ZBG1 φ45mm漏电&#xff08;剩余&#xff09;继电器 JHOK-ZBG2 φ45mm 漏电&#xff08;剩余&#xff09;继电器 JHOK-ZBG1 φ100mm漏电&a…

广西建筑模板厂家批发——能强优品木业

随着建筑业的蓬勃发展&#xff0c;建筑模板作为不可或缺的基础材料&#xff0c;在工程施工中扮演着重要的角色。在广西&#xff0c;能强优品木业有限公司作为一家备受瞩目的建筑模板厂家批发商&#xff0c;以其卓越的产品品质和优质的服务在行业中脱颖而出。 实力厂家扎根广西 …

电影《前任4:英年早婚》观后感

刚刚国庆节后&#xff0c;没有什么上新电影&#xff0c;只能看国庆节时的电影了&#xff0c;于是选择了这部《前任4&#xff1a;英年早婚》&#xff0c;原本是非常抗拒看这样的电影了&#xff0c;毕竟自己作为单身狗&#xff0c;还是不喜欢吃狗粮的&#xff0c;不过从另一方面讲…