多线程(初阶六:单例模式)

目录

一、单例模式的简单介绍

二、饿汉模式

三、懒汉模式

四、饿汉模式和懒汉模式的线程安全问题分析

1、饿汉模式(线程安全)

2、懒汉模式(线程不安全)

解决懒汉模式的线程安全问题

①给写操作打包成原子

②去除冗余操作

③存在指令重排序的问题

3、解决懒汉模式线程安全问题的最终代码:


一、单例模式的简单介绍

单例模式是一种设计模式,其中设计模式是软性的规定,与它关联的框架是硬性的规定,这些都是大佬已经设计好了的,即使是代码写的不是很好的菜鸡,按照这种模式也能写出还行的代码。类似象棋中的棋谱,即使你是新手,但按着棋谱走,你的棋力也不会太差。

单例 = 单个实例(对象)某个类,在一个线程中,只应该创建一个对象(原则上不应该有多个),这时就使用单例模式,就可以对我们的代码进行一个更严格的校验和检查

那么,怎么保证这一个对象唯一呢?

其一方法,可以通过“君子约定”,写一个文档,规定这个类只能有唯一的实例,新手程序猿接手这个代码时,就会发一份这个文档,进行约定,熟悉其中的规定、条约。

其二方法:可以让机器帮我们检查,人肯定是没有机器靠谱的,我们期望让机器帮我们对代码中指定的类,创建类的实例个数进行检查、校验,当创建的实例个数超过我们期望个数,就编译报错,这一点还是能实现的,其中单例模式就是已经设计好的套路,可以实现这种预期效果。


二、饿汉模式

饿汉模式是指创建实例是时期非常早,在类加载的时候,程序一启动,就已经创建好实例了,使用 “饿汉”这个词,就是形容创建实例非常迫切,非常早。下面实现一个单例模式

代码:

class Singleton {private static Singleton instance = new Singleton();public static Singleton getInstance() {return instance;}private Singleton(){ }
}
public class TestDemo4 {public static void main(String[] args) {}
}

当我们想在主线程中创建一个Singleton的实例时,会报错,如图:

singleton类的代码解析:

singleton内部,第一行代码就是这个,如图

这说明,singleton内部一开始就创建好了实例,创建实例非常迫切,使用static修饰说明instance是类属性。

接下来是获取这个类的实例方法,如图

因为我们不希望能创建出多个实例,所以就把singleton的构造方法用private来修饰,如图:

这样,如果我们想new一个Singleton对象,也new不了,但也有非正规手段,去获取singleton里面的属性或方法:反射。

最后,不管我们用getInstance获取多少次实例,获取的对象都是同一个对象,验证如下:

代码:

class Singleton {private static Singleton instance = new Singleton();public static Singleton getInstance() {return instance;}private Singleton(){ }
}
public class TestDemo4 {public static void main(String[] args) {Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();System.out.println(s1 == s2);}
}

执行结果:


三、懒汉模式

和饿汉模式不一样的是,创建实例的时机比较晚,没饿汉创建实例那么迫切,只有第一次使用这个类时,才会创建实例。

代码如下:

class SingletonLazy {private static SingletonLazy instance = null;public static SingletonLazy getInstance() {if(instance == null) {instance = new SingletonLazy();}return instance;}private SingletonLazy() { }
}
public class TestDemo5 {public static void main(String[] args) {}
}

代码解析:

一开始,没有创建实例,只是给singletonLazy赋值为null,并没有new一个对象,也就是没有创建实例;首次调用getInstance,instance是null的,所以会new一个对象,创建实例。如果不是第一次调用getInstance,就直接返回instance,这也保证了这个类的实例是唯一的,只有一个实例;

和饿汉模式的区别就是没那么迫切创建实例,等需要调用这个类的时候才创建一个实例,而饿汉模式是有了这个类就创建出实例。

懒汉模式的优点:有的程序,要在一定条件下,才需要进行相关的操作,有时候不满足这个条件,也就不需要完成这个操作了,这样,就把这个操作省下来了,而懒汉模式,就是这一思想,当需要这个实例时,才创建实例。像肯德基的疯狂星期四,只有在星期四的时候才会加载出相关信息,其他时间就不会加载。


四、饿汉模式和懒汉模式的线程安全问题分析

1、饿汉模式(线程安全)

代码分析:

当有多个线程,同时并发执行,调用getInstance方法,取instance,这时,线程是安全的吗?显然。这是线程安全操作,因为只涉及到读,多线程读取同一个变量,是线程安全的。而instance很早之前就已经创建好了,不会修改它,一直也只有这一个实例,也不涉及写的操作。

2、懒汉模式(线程不安全)

代码分析:

这里调用getInstance方法后,就会创建出实例来,那么我们想想,如果多个线程同时调用这个方法,此时SingletonLazy类里面的instance都为null,那么这些线程都会new对象,就会创建多个实例,这时,就不符合我们单例模式的预期了,所以,这个代码是线程不安全的。

这也是线程不安全的直接原因,就是 “写” 操作不是原子的。

解决懒汉模式的线程安全问题

①给写操作打包成原子

因为多线程并发执行的时候,可能读到的都是instance == null,所以会创建多个实例,那我们就给它加锁,让它在创建实例的时候,只能创建一个。

代码:

class SingletonLazy {private static Object locker = new Object();private static SingletonLazy instance = null;public static SingletonLazy getInstance() {synchronized (locker) {if(instance == null) {instance = new SingletonLazy();}}return instance;}private SingletonLazy() { }
}

这样,能让写操作的时候打包成一个原子,实例只可能创建一个的情况。

②去除冗余操作

以上操作加上了还是有问题:如果已经创建出实例了,我们还有加锁来判断它是不是null吗,加锁这些操作也是要消耗硬件资源的,没有必要为此浪费资源空间,如果已经不是null了,我们就想让它直接返回,不再进行加锁操作。

代码:

class SingletonLazy {private static Object locker = new Object();private static SingletonLazy instance = null;public static SingletonLazy getInstance() {if (instance == null) {synchronized (locker) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy() { }
}

注意看这里有两个判断条件是一样的,目前位置,也应该是我们第一次遇到这种代码,为什么要套两层一模一样的代码呢?要注意,这里的两个条件表达的都是不同的意思,第一个if条件判断是要让instance==null时,才要加锁的意思,提升代码的执行效率;而第二个if条件判断是把写操作打包成一个原子,保证线程安全。这两个if条件判断所表达的意思是不同的。

③存在指令重排序的问题

指令重排序:指令重排序也是编译器的一种优化,在保证原代码的逻辑不变,调整原代码的指令执行顺序,从而让程序的执行效率提高。

举个栗子:

现在我们要去菜市场买菜,买菜的清单:黄瓜,西红柿,萝卜,茄子,卖这些菜的位置分布图:

如果按照买菜清单顺序买,路线是这样的:

但是我们如果改变顺序,就能缩短我们买菜的时间了,如图:

指令重排序也是类似的道理,保证原代码的逻辑不变,改变原有指令的顺序,从而提高代码的执行效率,其中这个代码,就存在着指令重排序的优化,如图:

原本指令执行顺序:

1、去内存申请一段空间

2、在这个内存中调用构造方法,创建实例

3、从内存中取出地址,赋值给这个实例instance。

指令重排序后的顺序:1, 3 , 2

按照指令重排序后的代码执行逻辑就变成了这样:

假设有两个线程,现在执行顺序是这样的,如图:

因为指令重排序后,先去内存申请一段空间,然后是赋值给instance,那这时,instance就不是null了,第二个线程不会进入到if语句了,直接返回instance,可是instance还没有创建出实例,这样返回肯定是有问题的,这样,也就线程不安全了。

解决方案:给instance这个变量,加volatile修饰,强制取消编译器的优化,不能指令重排序,同时也排除了内存可见性的问题。

加volatile后的代码:

class SingletonLazy {private static Object locker = new Object();private static volatile SingletonLazy instance = null;public static SingletonLazy getInstance() {if (instance == null) {synchronized (locker) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy() { }
}

到这一步,才真的解决了懒汉模式的线程安全问题。

3、解决懒汉模式线程安全问题的最终代码:

class SingletonLazy {private static Object locker = new Object();private static volatile SingletonLazy instance = null;public static SingletonLazy getInstance() {if (instance == null) {synchronized (locker) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy() { }
}
public class ThreadDemo1 {public static void main(String[] args) {SingletonLazy s1 = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();System.out.println(s1 == s2);}
}

main线程是来测试每次拿到SingletonLazy的实例是不是同一个,

执行结果:符合我们的预期

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mendix组件推荐:灵活的在线表格

- 视频 mendix在线表格.mp4 20.95MB - 客户需求 如果你是一个中小型企业的负责人,你可能面临着: 多人协作录入数据展示数据库中的数据对数据安全有要求、希望本地离线部署并且IT人员配置有限等挑战 为了更好地管理你的业务数据,你需要一个…

zabbix 监控

zabbit 监控 非常成熟的监控软件。 运维人员,尽快系统服务器的状态,网站的流量,服务进程的运行状态。 保证整个集群的工作正常。7*24 zabbix是什么: web界面提供的一种可视化监控服务软件。 分布式的方式系统监控以及网络监控…

MySQL备份与恢复

一、逻辑备份 1、回顾什么是逻辑备份 逻辑备份就是把数据库、数据表或者数据进行导出,导出到一个文本文件中。 2、逻辑备份工具 mysqldump:提供全库级、数据库级别以及表级别的数据备份 mysqldump binlog二进制日志实现增量备份 3、逻辑的导出与导…

Spring Bean的生命周期各阶段详解附源码

目录 Bean的生命周期Bean定义阶段Bean实例化阶段Bean属性注入阶段Bean初始化阶段Bean销毁阶段 Bean的生命周期 bean的生命周期,我们都知道大致是分为:bean定义,bean的实例化,bean的属性注入,bean的初始化以及bean的销毁…

使用com组件编辑word

一个普通的窗体应用,6个button using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; u…

【C/PTA —— 14.结构体1(课内实践)】

C/PTA —— 14.结构体1(课内实践) 6-1 计算两个复数之积6-2 结构体数组中查找指定编号人员6-3 综合成绩6-4 结构体数组按总分排序 6-1 计算两个复数之积 struct complex multiply(struct complex x, struct complex y) {struct complex product;product.…

探索人工智能领域——每日20个名词详解【day8】

目录 前言 正文 总结 🌈嗨!我是Filotimo__🌈。很高兴与大家相识,希望我的博客能对你有所帮助。 💡本文由Filotimo__✍️原创,首发于CSDN📚。 📣如需转载,请事先与我联系以…

聊聊 Jetpack Compose 的 “状态订阅自动刷新” -- mutableStateListOf

Jekpack Compose “状态订阅&自动刷新” 系列: 【 聊聊 Jetpack Compose 的 “状态订阅&自动刷新” - - MutableState/mutableStateOf 】 【 聊聊 Jetpack Compose 的 “状态订阅&自动刷新” - - remember 和重组作用域 】 【 聊聊 Jetpack Compose 的 …

互联网Java工程师面试题·Spring Boot篇·第一弹

目录 1、什么是 Spring Boot? 2、Spring Boot 有哪些优点? 3、什么是 JavaConfig? 4、如何重新加载 Spring Boot 上的更改,而无需重新启动服务器? 5、Spring Boot 中的监视器是什么? 6、如何在 Sprin…

Bishop新著 - 深度学习:基础与概念 - 前言

译者的话 十几年前,笔者在MSRA实习的时候,就接触到了Christopher M, Bishop的经典巨著《Pattern Recogition and Machine Learning》(一般大家简称为PRML)。Bishop大神是微软剑桥研究院实验室主任,物理出身,对机器学习的基本概念…

根文件系统初步测试

一. 简介 上一篇文章学习了向所编译生成的根文件系统中加入 lib库文件。文章地址如下: 根文件系统lib库添加与初步测试-CSDN博客 本文继上一篇文章的学习,本文对之前制作的根文件系统进行一次初步测试。 二. 根文件系统初步测试 为了方便测试&#…

Oracle 11g安装过程

文章目录 前言1.下载安装包2.安装2.1本地安装文件2.2 安装过程 3.查看是否安装成功3.1 查看oracle是否安装成功3.2 查看oracle服务 前言 本文仅用于记录亲自安装oracle的过程 1.下载安装包 官网地址: Oracle Database 11g Release 2 (11.2.0.1.0) 注意&#xff…

08、分析测试执行时间及获取pytest帮助

官方用例 # content of test_slow_func.py import pytest from time import sleeppytest.mark.parametrize(delay,(1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,1.0,0.1,0.2,0,3)) def test_slow_func(delay):print("test_slow_func {}".format(delay))sleep(delay)assert…

java SSM毕业生信息管理myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

前言 学校的规模不断扩大,学生数量急剧增加,有关学生的各种信息量也成倍增长。面对庞大的信息量需要有学生信息管理系统来提高学生管理工作的效率。通过这样的系统可以做到信息的规范管理、科学统计和快速查询、修改、增加、删除等,从而减少管…

韶音开放式耳机怎么样?Oladance耳机怎么样?三大开放式对比!

当代耳机的痛点之一:舒适度,为什么我会这么说呢?大家应该也会感受到现在无论用的是半入耳,入耳式甚至是头戴式的耳机,都是会有一个问题:耳朵酸胀。因为我们无论是买一千的,还是几十的都是会痛&a…

进入软件的世界

选择计算机 上高中的时候,因为沉迷于网络游戏,于是对计算机产生了浓厚的兴趣,但是那个时候对于计算机的了解还是非常肤浅的。上大学的时候,也就义无反顾的选择了计算机专业,其实并不是一个纯粹的计算机专业&#xff0…

PWN学习之LLVM入门

一、基本流程 ①找到runOnFunction函数时如何重写的,一般来说runOnFunction都会在函数表最下面,找PASS注册的名称,一般会在README文件中给出,若是没有给出,可通过对__cxa_atexit函数"交叉引用"来定位: ②通…

全球与中国汽车电力电子市场:增长趋势、竞争格局与前景展望

目前,世界各国都致力于转向更环保、更永续的传统交通替代方案。 电动车满足所有要求,因为它们具有零废气排放、改善空气品质、减少温室气体排放并创造更清洁、更健康的环境。此外,电动车的运作成本比传统内燃机驱动的汽车低,因为…

vue2+electron桌面端一体机应用

vue2+electron项目 前言:公司有一个项目需要用Vue转成exe,首先我使用vue-cli脚手架搭建vue2项目,然后安装electron 安装electron 这一步骤可以省略,安装electron-builder时会自动安装electron npm i electron 安装electron-builder vue add electron-builder 项目中多出…

Mybatis 操作续集(连着上文一起看)

"查"操作(企业开发中尽量不使用*,需要哪些字段就写哪些字段,都需要就全写上) Mybatis 会自动地根据数据库的字段名和Java对象的属性名进行映射,如果名称一样就进行赋值 但是那些名称不一样的,我们想要拿到,该怎么拿呢? 一开始数据库字段名和Java对象属性名如下图…