多线程案例

  • 8.1 单例模式
    • 饿汉模式
    • 懒汉模式
      • 懒汉模式-单线程版
      • 懒汉模式-多线程版
      • 懒汉模式-多线程版(改进)

8.1 单例模式

单个实例. 在一个 java 进程中, 要求指定的类,只能有唯–个实例。(尝试 new 多个实例的时候, 就会直接编译报错)

单例模式是校招中最常考的设计模式之⼀.

啥是设计模式?
设计模式好⽐象棋中的 “棋谱”. 红⽅当头炮, ⿊⽅⻢来跳. 针对红⽅的⼀些⾛法, ⿊⽅应招的时候有⼀些固定的套路. 按照套路来⾛局势就不会吃亏.
软件开发中也有很多常⻅的 “问题场景”. 针对这些问题场景, ⼤佬们总结出了⼀些固定的套路. 按照这个套路来实现代码, 也不会吃亏.

单例模式能保证某个类在程序中只存在唯⼀⼀份实例, ⽽不会创建出多个实例.
这⼀点在很多场景上都需要. ⽐如 JDBC 中的 DataSource 实例就只需要⼀个.

什么场景适合使用单例模式?
代码中的有些对象,本身就不应该是有多个实例的.从业务角度就应该是单个实例.
在这里插入图片描述

单例模式具体的实现⽅式有很多. 最常⻅的是 “饿汉” 和 “懒汉” 两种.

饿汉模式

类加载的同时, 创建实例.

class Singleton {private static Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}

在这个类被加载的时候,就会初始化这个 静态成员。实例创建的时机非常早,就使用"饿汉!
在这里插入图片描述
万一,其他代码又 new 了这个类的实例咋办呢?需要禁止外部代码来创建该类的实例
在这里插入图片描述
虽然构造方法是 private,但是能否在类外面通过 反射 拿到私有构造方法创建实例??
原则上来说,可以做到。但是, 实际开发中, 反射不敢乱用的!!!反射属于非常规的编程,特殊场景下的特殊解决方案!!!! 使用反射要付出很大的代价(会严重影响代码的可读性和封装性)
类似于,通常情况下,你肯定没法直接闯入别人家里。但是, 你不能进, 不代表jc 蜀黍不能进,如果jc 蜀黍到处乱闯,当然也是不行的
代码中随便滥用反射,是非常糟糕的~~

懒汉模式

懒汉模式-单线程版

类加载的时候不创建实例. 第⼀次使⽤的时候才创建实例.

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

在计算机中,懒 的思想,就非常有意义
在这里插入图片描述
如果代码中存在多个单例类,使用饿汉模式,就会导致这些实例都是在程序启动的时候扎堆的创建的.可能把程序启动时间拖慢.
如果是懒汉模式,啥时候首次调用, 调用时机是分散的. 化整为零, 用户不太容易感知到"卡顿

如果是首次调用 getlnstance, 那么此时 instance 引用为 null,就会进入 if 条件,从而把实例创建出来,如果是后续再次调用 getlnstance, 由于 instance 已经不再是 null,此时不会进入if, 直接返回之前创建好的引用了。这样设定,仍然可以保证,该类的实例是唯一一个。与此同时,创建实例的时机就不是程序驱动时了,而是第一次调用getlnstance的时候
这个操作的执行时机就看你程序的实际需求。大概率要比饿汉这种方式要晚一些,甚至有可能整个程序压根用不到这个方法,也就把创建的操作给省下了

有的程序, 可能是根据一定的条件,来决定是否要进行某个操作,进一步的来决定创建某个实例
比如,肯德基有个操作“疯狂星期四”,对于 肯德基 点餐系统来说,就可以判定今天星期几。如果是星期四,才加载 疯狂星期四 相关的逻辑和数据,如果不是星期四,就不用加载了(节省了一定的开销)

懒汉模式-多线程版

上述的代码,饿汉模式和懒汉模式,是否是线程安全的?? 如果在多个线程中, 并发的调用 getlnstance, 这两个代码是否是线程安全的呢??
饿汉: getlnstance 直接返回 Instance 实例. 这个操作本质上就是"读操作"。多个线程读取同一个变量,是线程安全的!!
懒汉: 线程不安全,在多线程环境下可能会创建出多个实例!!在懒汉模式中,代码有读也有写,如果 t1 和 t2 按照下列顺序来执行,就会出现问题!!在这里插入图片描述

上⾯的懒汉模式的实现是线程不安全的.
线程安全问题发⽣在⾸次创建实例时. 如果在多个线程中同时调⽤ getInstance ⽅法, 就可能导致创建出多个实例.
⼀旦实例已经创建好了, 后⾯再多线程环境调⽤ getInstance 就不再有线程安全问题了(不再修改instance 了)

加上 synchronized 可以改善这⾥的线程安全问题.

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

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

多线程代码, 其实是非常复杂的,代码稍微变换一点,结论就截然不同!!
因此可千万不要以为,代码中写了 synchronized 就一定线程安全,不写 synchronized 就一定线程不安全!!!
一定要具体问题具体分析.要分析这个代码在各种调度执行顺序下可能的情况,确保每个情况都是正确的!!

此处要想让代码执行正确,其实是需要把 if 和 new 两个操作,打包成一个原子的!!
更加合理的做法,应该是把 synchronized 套到if 外头~~
在这里插入图片描述

但上述代码仍然存在问题~~

效率非常低!!!
如果 Instance 已经创建过了,此时后续再调用 getlnstance 就都是直接返回 Instance 实例了(此处的操作就是纯粹的读操作了,也就不会有线程安全问题了)
此时,针对这个已经没有线程安全问题的代码,仍然是每次调用都先加锁再解锁,此时,效率就非常低了!!!加锁就意味着可能会产生阻塞,一旦线程阻塞,啥时候能解除,就不知道了(你可以认为,只要一个代码里加锁了,基本就注定和“高性能"无缘)
在需要加锁的时候才加锁,不该加锁,不能随便乱加。所以除了 StringBuffer 还提供 StringBuilder, 除了 Vector 还提供 ArrayList
在这里插入图片描述

这个代码仍然有点问题~~

指令重排序,引起的线程安全问题
指令重排序,也是编译器优化的一种方式,调整原有代码的执行顺序,保证逻辑不变的前提下,提高程序的效率
instance = new singletonLazy();
这行代码,其实可以拆成三个大的步骤,(不是三个指令)
1.申请一段内存空间
2.在这个内存上调用构造方法,创建出这个实例
3.把这个内存地址赋值给 |nstance 引用变量
正常情况下,上述代码是按照 123 的顺序来执行的,但是编译器也可能会优化成132的顺序来执行,无论是123 还是132在单线程下都是可以的~~
1 就相当于是你买了个房子,2 就相当于给房子装修,3 就相当于你拿到房子的钥匙。123 拿到钥匙之后,就得到了装修好的房子. 称为"精装房",132你先拿钥匙,然后自己负责装修.称为"毛坏房"。如果你出去买房子,这两种情况都会存在!!!
但是, 如果是在多线程下,指令重排序,就可能引入问题了!!如果你出去买房子,这两种情况都会存在!!!
t1 按照132 的方式来执行这里的 new 操作
在这里插入图片描述
上述代码中,由于 t1 线程执行完13之后,调度走,此时 instance 指向的是一个 非 null 的,但是未初始化的对象。此时 t2 线程判定 instance == null 不成立,就会直接 return.如果 t2 继续使用 instance 里面的属性或者方法,就会出现问题(此时这里的属性都是未初始化的"全 0"值). 就可能会引起代码的逻辑出现问题.
解决上述问题,核心思路, 还是 volatile
volatile 有两个功能
1.保证内存可见性,每次访问变量必须都要重新读取内存,而不会优化到寄存器/缓存中
2.禁止指令重排序.针对这个呗 volatile 修饰的变量的读写操作相关指令,是不能被重排序的!!
在这里插入图片描述
在这里插入图片描述

以下代码在加锁的基础上, 做出了进⼀步改动:
• 使⽤双重 if 判定, 降低锁竞争的频率.
• 给 instance 加上了 volatile.

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

这个代码是一个经典高频面试题,非常重要,咱们同学们最近这几年秋招也会经常遇到这个问题~~
这个题并不简单。加上这三个点,怎么加,容易答上来,为啥要这么加,每个地方解决的什么问题,要想给面试官解释清楚,没那么容易的!!
(1)写博客,提前梳理好你都要说啥。
(2)给面试官讲的过程中,一定要多画图
线下面试,可以自带纸笔;线上面试,一般面试系统也会支持画图功能,可以共享屏幕。有的面试系统 牛客网面试系统,自身就支持画图,包括 腾讯会议,也支持画图
多去画!!!
目前来看线上面试越来越多,越是好的公司,越是线上面试
面试中考察的方法非常简单:
让你现场写一个单例模式的代码
这个代码咋写?直接就写成现在这个模样嘛??
正确的写法:
1.先写一个不带线程安全的单例模式
2.思索片刻, 线程不安全,把锁加上
3.再次思索片刻,加上 if(双重 if)
4. 再次思考片刻, 加上 volatile
意味着这个题不是你提前准备好,是你现场想出来的,面试官就会觉得,你这边很可能没有准备过/很久之前看的,即使如此,能够通过已经掌握的知识,推理出一些结论
一次写出最终版本,再面试官眼里,他觉得这个问题,你正好准备过,此时说明这个题目就考察不出来啥,这题不算,谈下一话题(面试的时候,大部分面试官,看到你的回答有问题的时候,都会进一步去问的)
人生如戏,全靠演技
把问题引导到你自己擅长的角度,把控整个面试的节奏~~

理解双重 if 判定 / volatile:
加锁 / 解锁是⼀件开销⽐较⾼的事情. ⽽懒汉模式的线程不安全只是发⽣在⾸次创建实例的时候. 因此后续使⽤的时候, 不必再进⾏加锁了.
外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了.
同时为了避免 “内存可⻅性” 导致读取的 instance 出现偏差, 于是补充上 volatile .
当多线程⾸次调⽤ getInstance, ⼤家可能都发现 instance 为 null, 于是⼜继续往下执⾏来竞争锁, 其中竞争成功的线程, 再完成创建实例的操作.
当这个实例创建完了之后, 其他竞争到锁的线程就被⾥层 if 挡住了. 也就不会继续创建其他实例.

  1. 有三个线程, 开始执⾏ getInstance , 通过外层的 if (instance == null) 知道了实例还没有创建的消息. 于是开始竞争同⼀把锁.
    在这里插入图片描述
  2. 其中线程1 率先获取到锁, 此时线程1 通过⾥层的 if (instance == null) 进⼀步确认实例是否已经创建. 如果没创建, 就把这个实例创建出来.
    在这里插入图片描述
  3. 当线程1 释放锁之后, 线程2 和 线程3 也拿到锁, 也通过⾥层的 if (instance == null) 来确认实例是否已经创建, 发现实例已经创建出来了, 就不再创建了
    在这里插入图片描述
  4. 后续的线程, 不必加锁, 直接就通过外层 if (instance == null) 就知道实例已经创建了,从⽽不再尝试获取锁了. 降低了开销.
    在这里插入图片描述

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

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

相关文章

【Python/Pytorch】-- 贝叶斯定理

文章目录 文章目录01 贝叶斯定理的理解02 在MRI重建领域应用01 贝叶斯定理的理解 贝叶斯定理的基本公式:P(A|B)P(B|A)*P(A) / P(B) 首先是如何理解这个公式? 在B事件发生的条件下,A发生的概率 P(A|B) 在B事件发生的条件下,A和B同…

子网掩码的隐形陷阱:为何能ping通却无法HTTPS访问

问题现象深度解析在近期企业网络维护中,运维团队发现一个具有教学意义的典型案例:某台部署在10.165.111.0/24网段的业务服务器(10.165.111.71)可以成功ping通目标中间件主机(10.165.110.11),但通过HTTPS协议访问https:…

【ArcGIS】如何编辑图层的属性表

GIS按属性选择后删除所选项呈现灰色_arcgis删除字段灰色-CSDN博客

大数据各组件flume,datax,presto,DolphinScheduler,findBI在大数据数仓架构中的作用和功能。

一、数据仓库核心价值铺垫在讲具体技术前,先明确数据仓库(Data Warehouse,简称数仓) 的核心作用: 数据仓库是 “整合企业多源数据、按业务主题组织、支持决策分析” 的结构化数据存储体系,核心价值是打破数…

React From表单使用Formik和yup进行校验

一、Formik的使用 官方文档地址:https://formik.org/docs/tutorial#validation 首先安装依赖 yarn add formik2.导入并初始化 import { useFormik } from formik; initialValues:初始化 输入框的密码和账号 onSubmit:当点击提交按钮时&am…

netty-scoket.io路径配置

1、服务端代码 package com.yh.service.socket;import com.corundumstudio.socketio.SocketIOServer; import com.corundumstudio.socketio.store.RedissonStoreFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory…

20250910荣品RD-RK3588-MID开发板在Android13系统下解决点卡迪的屏闪屏的问题

20250910荣品RD-RK3588-MID开发板在Android13系统下解决点卡迪的屏闪屏的问题 2025/9/5 15:44缘起:荣品RD-RK3588-MID开发板在Android13系统下解决点卡迪的屏。 按 POWER按键 关机之后,2s之内再次短按 POWER按键,开机之后屏会抖动。 2s后短按…

正态分布 - 计算 Z-Score 的 无偏估计

正态分布 - 计算 Z-Score 的 无偏估计 flyfish Z-Score公式与计算步骤 1 公式(样本Z-Score) 实际应用中,我们几乎不知道“总体均值/标准差”,所以常用样本数据计算: zixi−xˉsz_i \frac{x_i - \bar{x}}{s}zi​sxi​−…

ai生成文章,流式传输(uniapp,微信小程序)

1.环境nutui-uniappvue3tsunocss2.功能源码包含ai生成逻辑&#xff0c;内容生成实时打字机功能&#xff0c;ai数据处理等<script setup lang"ts"> import {queryAIParams, } from /api/pagesA import { submitFn } from /api/aiimport Navbar from /component…

Linux设备内存不足如何处理

[rootlocalhost ~]# free -mtotal used free shared buff/cache available Mem: 31208 14317 1280 1551 15610 14657 Swap: 15927 2781 13146 [rootlocalhost ~]#从 free -m 输出来看&…

中间件八股

文章目录RedisRedis为什么快&#xff1f;Redis Redis为什么快&#xff1f; 首先它是内存数据库&#xff0c;所有数据直接操作内存而非磁盘&#xff0c;避免了 I/O 瓶颈&#xff1b;其次采用单线程模型&#xff0c;消除了多线程切换的开销&#xff0c;同时通过非阻塞 I/O 多路…

【参数详解与使用指南】PyTorch MNIST数据集加载

# 加载MNIST数据集 train_dataset datasets.MNIST(root./data, trainTrue, downloadTrue, transformtransform) # 下载训练集 test_dataset datasets.MNIST(root./data, trainFalse, downloadTrue, transformtransform) # 下载测试集在深度学习入门过程中&#xff0c;MNIST手…

闭包面试题

闭包&#xff08;Closure&#xff09; 是指一个函数能够记住并访问其词法作用域&#xff08;定义时的作用域&#xff09;&#xff0c;即使该函数在其词法作用域之外执行。一、通俗理解&#xff08;面试可这样开头&#xff09;&#xff1a;> 闭包就是一个函数“记住”了它出生…

WebSocket 双向通信实战:SCADA 移动端实时操控响应优化

引言&#xff1a;SCADA 移动端的 “延迟烦恼” 与破局之道在电力调度、水厂监控、智能制造等场景中&#xff0c;SCADA 系统&#xff08;数据采集与监视控制系统&#xff09;是当之无愧的 “工业指挥官”—— 它能实时采集设备运行数据&#xff08;如电网负荷、水泵压力、机床转…

SafeEar:浙大和清华联合推出的AI音频伪造检测框架,错误率低至2.02%

本文转载自&#xff1a;https://www.hello123.com/safeear ** 一、&#x1f512; SafeEar&#xff1a;你的声音 “防火墙”&#xff0c;让 AI 伪造音频无所遁形 担心自己的声音被 AI 模仿甚至伪造&#xff1f;SafeEar就是来帮你解决这个难题的&#xff01;它是由浙江大学和清…

uni-app iOS 日志与崩溃分析全流程 多工具协作的实战指南

在 uni-app 跨平台开发中&#xff0c;iOS 应用的日志与崩溃分析往往是开发者最头疼的问题。 日志分散&#xff1a;uni-app 的 JS 日志、原生插件日志、系统日志分布在不同位置&#xff1b;崩溃难复现&#xff1a;用户反馈的崩溃往往无法在开发机还原&#xff1b;符号化复杂&…

CSS定义网格的列模板grid-template-columns什么意思,为什么要用这么复杂的单词

这个词确实看起来复杂&#xff0c;但其实很好理解。让我来拆解一下&#xff1a;单词分解grid-template-columns grid - 网格template - 模板columns - 列连起来就是&#xff1a;网格模板列 → 定义网格的列模板为什么要用这么长的单词&#xff1f;语义明确&#xff1a;长单词能…

Umi-OCR:Windows7和Linux上可免费离线使用的OCR应用!

工具介绍 Umi-OCR 是一款免费、开源的离线OCR软件&#xff0c;主要由作者 hiroi-sora 用业余时间在开发和维护。 Umi-OCR 内置多国语言库&#xff0c;支持截屏/批量导入图片&#xff0c;PDF文档识别&#xff0c;排除水印/页眉页脚以及二维码的扫描/生成。 适用平台&#xff1…

30 分钟让 AI 开口查订单:React-Native + Coze 全链路语音对话落地指南

一、前言&#xff1a;为什么你需要“可说话、能查库”的 AI&#xff1f; 聊天机器人在 2025 已不新鲜&#xff0c;但**“张嘴就能查询私有业务数据”**的端到端方案依然踩坑无数&#xff1a; ASR/TTS 选型多、SDK 难对齐大模型与内部 API 安全打通RN 端流式渲染 音频播放并发…

玄机--应急响应--webshell查杀

靶场连接1.黑客webshell里面的flag flag{xxxxx-xxxx-xxxx-xxxx-xxxx}使用命令查找特殊文件//搜索目录下适配当前应用的网页文件&#xff0c;查看内容是否有Webshell特征 find ./ type f -name "*.jsp" -exec grep -l "exec(" {} \; find ./ type f -name &…