多线程 - 单例模式

v2-b6f7ee5f6c7a9e66581bda987710eb4f_b0213

单例模式 ~~ 单例模式是常见的设计模式之一

什么是设计模式

你知道象棋,五子棋,围棋吗?如果,你想下好围棋,你就不得不了解一个东西,”棋谱”,设计模式好比围棋中的 “棋谱”.
在棋谱里面,大佬们,把一些常见的对局场景,都给推演出来了,照着棋谱来下棋,基本上棋力就不会差到哪里去.
同理,软件开发中也有很多常见的 “问题场景”, 针对这些问题场景, 大神们总结出了一些固定的套路, 按照这个套路来实现代码, 写的代码就不会太差.
设计模式就是针对一些典型的场景,给出了一些典型的解决方案.

单例模式

单例模式 => 单个实例(对象)
~~ 通过巧用Java的现有语法,达成了某个类只能被创建出一个实例这样的效果,当我们不小心创建了多个实例,就会编译报错.

场景: 很多场景广泛,比如JDBC中DataSource这样的类,其实就非常适合于使用单例模式.

单例模式具体的实现方式, 分成 “饿汉” 和 “懒汉” 两种
注: 其实在Java里实现单例模式的方式有很多种,只是这两种最常见.

饿汉模式

类加载阶段,就把实例创建出来了(类加载是比较靠前阶段),这种效果,就给人一种"特别急切”的感觉,就像一个饿了很久的人,看到吃的,就会很急切,这种感觉就给它起了一个形象的名字,叫做“饿汉模式”,还有一个原因就是与后文讲解的“懒汉模式”相对应.

class Singleton {// 在此处, 先把这个实例给创建出来了private static Singleton instance = new Singleton();// 被 static 修饰的 Singleton 这个属性和实例无关,而是和类有关/** java 代码中的每个类,都会在编译完成后得到.class 文件.* JVM 运行是就会加载这个 .class 文件读取其中的二进制指令,并且在内存中* 构造出对应的类对象.(形如 Singleton.class) => * *//** 由于类对象 在一个 java 进程里,只是有唯一一份的* 因此类对象内部的类属性也是唯一一份了* */// 如果需要使用这个唯一实例, 统一通过 Singleton.getInstance() 方式来获取对象public static Singleton getInstance() {return instance;}// 为了避免 Singleton 类不小心被复制出多份来.// 把构造方法设为 private, 在类外面,就无法通过 new 的方式来创建这个 Singleton 实例了private Singleton() {}
}

如何保证实例唯一的

  1. static这个操作,是让当前instance属性是类属性了.
    类属性是在类对象上的,类对象又是唯一实例的(只是在类加载阶段被创建出一个实例)
    • 注: 类属性和类对象是一 一对应的,即类对象如果是多个了,类属性也是就有多份了,此时类对象就不是单例的了.
  2. 构造方法是设为private.外面的代码中无法new.

类加载阶段

运行一个Java程序,就需要让Java进程能够找到并读取对应的.class文件,就会读取文件内容,并解析,构造成类对象…这一系列的过程操作,称为类加载.

懒汉模式的实现

这个实例并非是类加载的时候创建了,而是真正第一次使用的时候,才去创建(如果不用,就不创建了 => “懒”).
注: 在计算机中,懒,往往是褒义词,勤快,才是贬义词 ~~ 从"效率"上考虑,懒汉模式比饿汉模式更胜一筹!!!

懒汉模式-单线程版

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

上述写的饿汉模式和懒汉模式,如果在多线程环境下调用getInstance,是否是线程安全的?

image-20231001135552693

if (instance == null) { instance = new SingletonLazy(); } return instance;

if (instance == null) {instance = new SingletonLazy();}return instance;

image-20231001142343309

刚才线程安全问题,本质是读,比较和写这三个操作不是原子的,这就导致了t2读到的值可能是t1还没来得及写的(脏读)

懒汉模式-多线程版

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

注: 加锁了之后,确保了此时的读操作和修改操作是一个整体.
image-20231001151835949

懒汉模式-多线程版(改进)

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

上述代码就导致每次getlnstance都需要加锁(加锁操作是有开销的).
问题来了: 真的需要每次加锁吗?
是不需要的,这里的加锁只是在new出对象之前加上,是有必要的.
一旦对象new完了,后续调用getlnstance,此时instance的值一定是非空的,因此就会直接触发return.
相当于一个是比较操作,一个是返回操作,这两个操作都是读操作,此时不加锁也是OK的

解决: 基于上述讨论,就可以给上面的代码加上一个判定:
如果对象还没创建,才加锁;
如果对象已经创建过了,就不加锁了.

public static SingletonLazy getInstance() {if (instance == null) { // 此处不再是无脑加锁了而是满足了特定条件之后,才真正加锁.synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}}}return instance;
}

解析: 如果这两个条件中间没有加锁,连续两个相同的 if 是没意义的.
但是有了加锁,就不一定了,加锁操作可能会引起线程阻塞.当执行到锁结束,再执行到第二个if 的时候,
第二个 if 和第一个 if 之间可能已经隔了很久的时间,沧海桑田.
程序的运行内部的状态,这些变量的值,都可能已经发生很大改变了.
注: 第一个 if 条件 负责判定是否要加锁, 第二个 if 条件负责判定是否要创建对象.这两个 if 条件的目的是完全不同的,只不过由于巧合,代码是一样的.

上述懒汉模式的代码,还有内存可见性问题&指令重排序问题待解决!

可见性问题
假设有很多线程,都去进行getInstance,这个时候,是否就会有被优化的风险呢?
(只有第一次读才是真正读了内存,后续都是读寄存器/cache)

指令重排序问题

instance new Singleton();
拆分成三个步骤:
1.申请内存空间.
2.调用构造方法,把这个内存空间初始化成一个合理的对象.
3.把内存空间的地址赋值给 instance 引用.

正常情况下,是按照123这个顺序来执行的,但是编译器还有一手操作,指令重排序为了提高程序效率,调整代码执行顺序,123这个顺序就可能变成132.
如果是单线程,123和132没有本质区别,但是多线程环境下,就会存在问题!!!
假设t1是按照132的步骤执行的.t1执行到13之后,执行2之前,被切出CPU, t2 来执行(当t1执行完13之后, 在t2看来,此处的引用就非空了), 此时此刻, t2就相当于直接返回了instance引用并且可能会尝试使用引用中的属性. 但是由于t1中的2操作还没执行完呢, t2拿到的是非法的对象,还没构造完成的不完整的对象.

volatile

volatile有两个功能:

  1. 解决内存可见性
    2.禁止指令重排序

完全体的单例模式(懒汉模式)代码

class SingletonLazy {private volatile static SingletonLazy instance = null;// 1public static SingletonLazy getInstance() {if (instance == null) {// 2synchronized (SingletonLazy.class) {// 3if (instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy() {}
}

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

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

相关文章

Scala第十五章节

Scala第十五章节 1. 递归 2. 案例一: 求阶乘 3. 案例二: 斐波那契数列 4. 案例三: 打印目录文件 scala总目录 文档资料下载

机器学习必修课 - 如何处理缺失数据

运行环境:Google Colab 处理缺失数据可简单分为两种方法:1. 删除具有缺失值的列 2. 填充 !git clone https://github.com/JeffereyWu/Housing-prices-data.git下载数据集 import pandas as pd from sklearn.model_selection import train_test_split导…

竞赛 机器视觉 opencv 深度学习 驾驶人脸疲劳检测系统 -python

文章目录 0 前言1 课题背景2 Dlib人脸识别2.1 简介2.2 Dlib优点2.3 相关代码2.4 人脸数据库2.5 人脸录入加识别效果 3 疲劳检测算法3.1 眼睛检测算法3.2 打哈欠检测算法3.3 点头检测算法 4 PyQt54.1 简介4.2相关界面代码 5 最后 0 前言 🔥 优质竞赛项目系列&#x…

C语言实例_调用SQLITE数据库完成数据增删改查

一、SQLite介绍 SQLite是一种轻量级的关系型数据库管理系统(RDBMS),它是一个开源的、零配置的、服务器端的、自包含的、零管理的、事务性的SQL数据库引擎。它被广泛应用于嵌入式设备、移动设备和桌面应用程序等领域。 SQLite的特点包括&…

SpringBoot快速入门

搭建SpringBoot工程,定义hello方法,返回“Hello SpringBoot” ②导入springboot工程需要继承的父工程;以及web开发的起步依赖。 ③编写Controller ④引导类就是SpringBoot项目的一个入口。 写注解写main方法调用run方法 快速构建SpringBoo…

MySQL体系结构和四层架构介绍

MySQL体系结构图如下: 四层介绍 1. 连接层: 它的主要功能是处理客户端与MySQL服务器之间的连接(比如Java应用程序通过JDBC连接MySQL)。当客户端应用程序连接到MySQL服务器时,连接层对用户进行身份验证、建立安全连接并管理会话状态。它还处理…

python获取时间戳

使用 datetime 库获取时间。 获取当前时间: import datetime print(datetime.datetime.now()) . 后面的是微秒,也是一个时间单位,1秒1000000微秒。 转为时间戳: import datetimedate datetime.datetime.now() timestamp date…

小谈设计模式(14)—建造者模式

小谈设计模式(14)—建造者模式 专栏介绍专栏地址专栏介绍 建造者模式角色分类产品(Product)抽象建造者(Builder)具体建造者(Concrete Builder)指挥者(Director&#xff0…

电脑通过串口助手和51单片机串口通讯

今天有时间把电脑和51单片机之间的串口通讯搞定了,电脑发送的串口数据,单片机能够正常接收并显示到oled屏幕上,特此记录一下,防止后面自己忘记了怎么搞得了。 先来两个图片看看结果吧! 下面是串口3.c的文件全部内容&a…

Spring Cloud Zuul 基本原理

Spring Cloud Zuul 底层是基于Servlet实现的,核心是通过一系列的ZuulFilter来完成请求的转发。 1、核心组件注册 1.1. EnableZuulProxy注解 启用Zuul作为微服务网关,需要在Application应用类加上EnableZuulProxy注解,而该注解核心是利用Im…

@SpringBootApplication注解的理解——如何排除自动装配 分布式情况下如何自动加载 nacos是怎么被发现的

前言 spring作为主流的 Java Web 开发的开源框架,是Java 世界最为成功的框架,持续不断深入认识spring框架是Java程序员不变的追求。 本篇博客介绍SpringBootApplicant注解的自动加载相关内容 其他相关的Spring博客文章列表如下: Spring基…

2023 年热门的大型语言模型 (LLMs)汇总【更新至9月26】

一、全景地图 整理了一张大语言模型的血缘图谱,如下图所示: 图中的大语言模型,都是自己做过评测的,主观了点,但是原汁原味,有好的可以推荐给我。 二、ChatGPT系列 ChaTGP是商业版本大语言模型的正统&…

逆强化学习

1.逆强化学习的理论框架 1.teacher的行为被定义成best 2.学习的网络有两个,actor和reward 3.每次迭代中通过比较actor与teacher的行为来更新reward function,基于新的reward function来更新actor使得actor获得的reward最大。 loss的设计相当于一个排序问…

visual studio禁用qt-vsaddin插件更新

visual studio里qt-vsaddin插件默认是自动更新的,由于qt-vsaddin插件新版本的操作方式与老版本相差较大,且新版本不稳定,容易出Bug,所以需要禁用其自动更新,步骤如下:     点击VS2019菜单栏上的【扩展】–…

基于Java的毕业设计选题管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序(小蔡coding)有保障的售后福利 代码参考源码获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

实现springboot的简单使用~

在之前学习SpringSpringMVCMybatis框架时,我们学习了多种配置spring程序的方式,例如:使用XML,注解,Java配置类,或者是将它们结合使用,但配置文件配置起来依然过于复杂,而我们接下来要…

虚拟机VMware的使用流程以及出现的问题附解决方法

虚拟机VMware的使用流程以及出现的问题附解决方法 下载安装 略。。。 创建虚拟机 虚拟机的设置如下:注意网络适配器为NAT 如果出现ip addr 命令:不显示IP地址的话: 解决方式如下: 首先设置网卡:先查看一下onboot是…

软件工程与计算总结(三)示例项目描述

本节介绍一个标准的项目描述,大家可以作为蓝本学习~ 目录 一.背景 二.目标 三.系统用户 四.用户访谈要点 1.收银员 2.客户经理 3.总经理 4.系统管理员 五.项目实践过程 一.背景 A是一家刚刚发展起来的小型连锁商店,其前身是一家独立的小百货门面…

贪心算法+练习

正值国庆之际,祝愿祖国繁荣昌盛,祝愿朋友一生平安!终身学习,奋斗不息! 目录 1.贪心算法简介 2.贪心算法的特点 3.如何学习贪心算法 题目练习(持续更新) 1.柠檬水找零(easy&…

Altium Designer 批量添加元器件后缀

Altium Designer 批量添加元器件后缀 方法一方法二可能出现的问题要注意 方法一 您可以使用 Altium Designer 中的“批量修改元器件名称”功能来批量添加元器件后缀。具体步骤如下: 1.为了方便显示 操作流程,我这里复制了几个原理图的文件,粘…