当前位置: 首页 > news >正文

多线程案例 - 单例模式

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() {}
}

相关文章:

多线程案例 - 单例模式

单例模式 ~~ 单例模式是常见的设计模式之一 什么是设计模式 你知道象棋,五子棋,围棋吗?如果,你想下好围棋,你就不得不了解一个东西,”棋谱”,设计模式好比围棋中的 “棋谱”. 在棋谱里面,大佬们,把一些常见的对局场景,都给推演出来了,照着棋谱来下棋,基本上棋力就不会差到哪…...

云原生Kubernetes:对外服务之 Ingress

目录 一、理论 1.Ingress 2.部署 nginx-ingress-controller(第一种方式) 3.部署 nginx-ingress-controller(第二种方式) 二、实验 1.部署 nginx-ingress-controller(第一种方式) 2.部署 nginx-ingress-controller(第二种方式) 三、问题 1.启动 nginx-ingress-controll…...

Java21 新特性

文章目录 1. 概述2. JDK21 安装与配置3. 新特性3.1 switch模式匹配3.2 字符串模板3.3 顺序集合3.4 记录模式(Record Patterns)3.5 未命名类和实例的main方法(预览版)3.6 虚拟线程 1. 概述 2023年9月19日 ,Oracle 发布了…...

Rest Template 使用

大家好我是苏麟 今天带来Rest Template . spring框架中可以用restTemplate来发送http连接请求, 优点就是方便. Rest Template 使用 Rest Template 使用步骤 /*** RestTemple:* 1.创建RestTemple类并交给IOC容器管理* 2. 发送http请求的类*/ 1.注册RestTemplate对象 SpringB…...

IDEA git操作技巧大全,持续更新中

作者简介 目录 1.创建新项目 2.推拉代码 3.状态标识 5.cherry pick 6.revert 7.squash 8.版本回退 9.合并冲突 1.创建新项目 首先我们在GitHub上创建一个新的项目,然后将这个空项目拉到本地,在本地搭建起一个maven项目的骨架再推上去&#xff0…...

计算机操作系统 (王道考研)笔记(四)I/O系统

目录 1 I/O1.1 I/O 概念和分类1.1.1 I/O 定义1.1.2 I/O 分类 1.2 I/O控制器1.3 I/O 软件层次结构1.4 I/O 应用程序接口和驱动程序应用接口 1 I/O 1.1 I/O 概念和分类 1.1.1 I/O 定义 BIOS(英文:Basic Input/Output System),即基…...

【Java基础】抽象类和接口的使用

个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【JavaSE_primary】 本专栏旨在分享学习JavaSE的一点学习心得,欢迎大家在评论区讨论💌 目录 一、抽象类抽象类概念…...

Golang的性能优化

欢迎,学习者们,来到Golang性能优化的令人兴奋的世界!作为开发者,我们都努力创建高效、闪电般快速的应用程序,以提供出色的用户体验。在本文中,我们将探讨优化Golang应用程序性能的基本技巧。所以&#xff0…...

实现两栏布局的五种方式

本文节选自我的博客:实现两栏布局的五种方式 💖 作者简介:大家好,我是MilesChen,偏前端的全栈开发者。📝 CSDN主页:爱吃糖的猫🔥📣 我的博客:爱吃糖的猫&…...

博物馆门票预约APP的设计与实现(源码+lw+部署文档+讲解等)

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

【AI视野·今日Robot 机器人论文速览 第四十四期】Fri, 29 Sep 2023

AI视野今日CS.Robotics 机器人学论文速览 Fri, 29 Sep 2023 Totally 38 papers 👉上期速览✈更多精彩请移步主页 Interesting: 📚NCF,基于Neural Contact Fields神经接触场的方法实现有效的外部接触估计和插入操作。 (from FAIR ) 操作插入处理结果&am…...

一维数组和二维数组的使用(char类型)

目录 导读1. 字符数组1.1 字符数组的创建1.2 字符数组的初始化1.3 不同初始化在内存中的不同1.3.1 strlen测试1.3.2 sizeof测试1.3.3 差异原因 1.4 字符数组的使用 2. 数组越界3. 数组作为函数参数博主有话说 导读 我们在前面讲到了 int 类型的数组的创建和使用: 一…...

1.基本概念 进入Java的世界

1.1 Java的工作方式 1.2 Java的程序结构 类存于源文件里面,方法存于类中,语句(statement)存于方法中 源文件(扩展名为.java)带有类的定义。类用来表示程序的一个组件,小程序或许只会有一个类…...

程序在线报刊第一期

文章目录 程序在线报刊第一期排序算法:优化数据处理效率的核心技术回顾区块链技术:去中心化引领数字经济新时代展望AI未来:智能化时代的无限可能 程序在线报刊第一期 排序算法:优化数据处理效率的核心技术 近年来,随…...

k8s 拉取镜像报错 no basic auth credentials

文章目录 [toc]基于现有凭据创建 Secret通过命令行创建 Secretpod 使用指定 secret 认证私有镜像仓库 省流提醒: 本次解决的问题是 docker login 可以正常登录,docker pull 也可以正常拉取镜像,只是 k8s 在启动 pod 的时候,没有指…...

Koa处理请求数据

在开发中,后端接收到请求参数后,需要解析参数。请求分为很多种类型,比如常见的get和post。 请求参数 Koa本身可以解析get请求参数,不能解析post请求参数。例如: router.get(/api/get/userInfo, async (context) >…...

关于浮点数的 fld、fadd、fstp 汇编指令介绍

文章目录 FLDFADDFSTP FLD, FADD 和 FSTP 常在一起出现&#xff0c;用于 float 运算。组合实现浮点数的加载、加法运算和保存 FLD FLD 指令用于将 浮点数 从内存加载到浮点寄存器栈&#xff08;FPU Stack&#xff09;中。它的使用方式如下&#xff1a; FLD <源内存地址&g…...

知识图谱小白入门(1):neo4j的安装与CQL的使用

文章目录 序一、安装neo4j1.1 下载neo4j1.2 安装JDK1.3 BUG&#xff1a;dbms failed to start 二、CQL语法2.1 CQL语法创建节点查询节点创建关系查询关系2.2 习题 习题答案 序 知识图谱&#xff0c;是一种实体间的信息与关系知识的网状结构&#xff0c;借用图论中点与边的概念…...

一个用java的get请求

java发送一个get请求&#xff0c;请求参数classyanfa&#xff0c;使用Authorization认证&#xff0c;在Request Header里填充Authorization&#xff1a; Bearer {token}进行请求认证&#xff0c;token为&#xff1a;sadagdagdgdgfagfd ,另外在Header里补充App标识&#xff0c;X…...

作为SiteGPT替代品,HelpLook的优势是什么?

在当今快节奏的数字化世界中&#xff0c;企业不断寻求创新方式来简化运营并增强客户体验。由于聊天机器人能够自动化任务、提供快速响应并提供个性化互动&#xff0c;它们在业务运营中的使用变得非常重要。因此&#xff0c;企业越来越意识到像SiteGPT和HelpLook这样高效的聊天机…...

uni-app:实现页面效果2(canvas绘制,根据页面宽度调整元素位置)

效果 代码 <template><view><!-- 车搭配指示器-双显 --><view class"content_position"><view class"content"><view class"SN"><view class"SN_title">设备1</view><view class…...

【24种设计模式】责任链模式(Chain of Responsibility Pattern)

责任链模式是一种行为设计模式&#xff0c;它允许你将请求沿着处理链进行传递&#xff0c;直到有一个处理者能够处理该请求为止。这种模式将请求的发送者和接收者解耦&#xff0c;使多个对象都有机会处理该请求。 责任链模式的结构 责任链模式由以下几个角色组成&#xff1a;…...

微信小程序一对多个页面间传递数据进行通信,事件触发的实现方法

在有些场景下&#xff0c;使用组件间通信或者组件和页面间通信并不可行。比如我需要只有一个websocket实例&#xff0c;并且需要向多个页面传递我接收到的消息。 下面的方法是一个页面向其它页面&#xff08;触发事件&#xff09;传递数据&#xff0c;其它页面也可以向这个页面…...

软件测试之Python基础学习

目录 一、Python基础 Python简介、环境搭建及包管理 Python简介 环境搭建 包管理 Python基本语法 缩进(Python有非常严格的要求) 一行多条语句 断行 注释 变量 基本数据类型(6种) 1. 数字Number 2. 字符串String 3. 列表List 4. 元组Tuple 序列相关操作方法 …...

模块化编程+LCD1602调试工具——“51单片机”

各位CSDN的uu们你们好呀&#xff0c;小雅兰又来啦&#xff0c;刚刚学完静态数码管显示和动态数码管显示&#xff0c;感觉真不错呢&#xff0c;下面&#xff0c;小雅兰就要开始学习模块化编程以及LCD1602调试工具的知识了&#xff0c;让我们进入51单片机的世界吧&#xff01;&am…...

【Linux】UDP的服务端 + 客户端

文章目录 &#x1f4d6; 前言1. TCP和UDP2. 网络字节序2.1 大小端字节序&#xff1a;2.2 转换接口&#xff1a; 3. socket接口3.1 sockaddr结构&#xff1a;3.2 配置sockaddr_in&#xff1a;3.3 inet_addr&#xff1a;3.4 inet_ntoa&#xff1a;3.5 bind绑定&#xff1a; 4. 服…...

德国自动驾驶卡车公司【Fernride】完成1900万美元A轮融资

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于德国沃尔夫斯堡的自动驾驶卡车公司【Fernride】今日宣布已完成1900万美元A轮融资&#xff0c;本轮融资完成后Fernride的融资金额已经达到了达到5000万美元。 本轮融资由Deep Tech and Cli…...

实现水平垂直居中的十种方式

本文节选自我的博客&#xff1a;实现水平垂直居中的十种方式 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是MilesChen&#xff0c;偏前端的全栈开发者。&#x1f4dd; CSDN主页&#xff1a;爱吃糖的猫&#x1f525;&#x1f4e3; 我的博客&#xff1a;爱吃糖的猫&…...

头条号热点采集工具-头条号热文采集软件

有一种魔法&#xff0c;能让信息传遍大地&#xff0c;让新闻在互联网上迅速传播&#xff0c;引发关注和讨论&#xff0c;那就是头条热点。无论你是一名自媒体创作者&#xff0c;还是一个信息追踪者&#xff0c;头条热点都是你不能忽视的宝贵资源。然而&#xff0c;如何获取这些…...

了解”变分下界“

“变分下界”&#xff1a;在变分推断中&#xff0c;我们试图找到一个近似概率分布q(x)来逼近真实的概率分布p(x)。变分下界是一种用于评估近似概率分布质量的指标&#xff0c;通常用来求解最优的近似分布。它的计算涉及到对概率分布的积分或期望的估计...

有哪些做家教网站/聊城seo优化

问题1&#xff1a;在服务器A上有自己需要的代码&#xff0c;但是自己的服务器B上没有代码&#xff0c;因此希望从A服务器上拷贝代码到服务器B上&#xff08;使用的是LINUX操作系统&#xff09;解决办法&#xff1a;使用 scp 命令.e5e587:~/e5/private/huangbiao/E587_11.203.22…...

陆川建设局网站/电商平台怎么注册

一、全国增值电信业务许可情况截至2020年5月底&#xff0c;全国增值电信业务经营许可企业共84042家&#xff0c;比上月增长2.02%&#xff0c;其中工业和信息化部许可的跨地区企业20391家&#xff0c;各省(区、市)通信管理局许可的本地企业合计63651家&#xff0c;分别比上月增长…...

专门做中式服装平台的网站/郑州网络推广公司

PHP - Manual手册 - CLXI. String 字符串处理函数 - str_pad使用另一个字符串将一个字符串填充到指定长度 PHP: str_pad - Manual: http://www.php.net/manual/zh/function.str-pad.php [PHP - Manual手册] Download下载, http://www.php.net/download-docs.php PHP: 异常…...

网站建设与维护要用到代码吗/宁波网站建设公司

先来讲什么是线程&#xff1a; 即&#xff1a;Thread和Runnable两个类&#xff0c;可以实现线程 class Card extends Thread{ //第一步&#xff0c;重写父类Thread中的run方法&#xff0c;这样就可以调度线程&#xff0c;调度线程中启动的方法&#xff0c;即run方法&#xff1a…...

flash企业网站模板php/做关键词排名好的公司

for循环嵌套 for(int x 0 ;x < 3 ;x){for(int y 0;y<4;y){System.out.print("*"); //输出*}System.out.println(); //换行} //外层循环每执行一次&#xff0c;内层循环执行完一次/*需求&#xff1a;在屏幕中显示出一下图形&#xff1a;***************思路&…...

广州花都网站建设/百度搜索网页版入口

#include<algorithm> <numeric> <functional> 遍历算法 for_each: vector<int>v; v.push_back(1);v.push_back(2);v.push_back(3); for_each(v.begin(),v.end(),普通函数); for_each(v.begin(),v.end(),仿函数); transform:搬运容器到另外一个容…...