《javaEE篇》--单例模式详解
目录
单例模式
饿汉模式
懒汉模式
懒汉模式(优化)
指令重排序
总结
单例模式
单例模式属于一种设计模式,设计模式就好比是一种固定代码套路类似于棋谱,是由前人总结并且记录下来我们可以直接使用的代码设计思路。
单例模式就是,在有些场景中希望一个类只能有一个对象,不能有多个,这时你可能会觉得,这还不简单,我保证自己只new一个对象不就好了,但是你能保证自己只new一个对象,但是你能保证别人不会new对象吗?又或者,你真的能确保自己只new一个对象吗?所以“我保证自己只new一个对象不就好了”,这只是一个君子协定,光靠人是非常不靠谱的,所以我们要依靠计算机来帮助我们实现这个协定。
//这一点在很多场景上都需要. 比如 JDBC 中的 DataSource 实例就只需要一个.
单例模式的实现方式可以分为两种,饿汉模式和懒汉模式
饿汉模式
具体实现方法
- 用static修饰,在类的内部创建一个现成的实例,让对象在在类加载时就被创建
- 提供一个方法当需要这个对象时就通过这个方法获得
- 用private修饰构造方法,这样外界就不能实例化这个类了
通过这三个方法就可以保证只有一个该类对象了
class Singleton{//创建时时机比较早(饿汉模式)//当类被加载时就会执行这里的创建实例操作private static Singleton instance = new Singleton();//后续需要这个对象,都通过这个方法获取public static Singleton getInstance(){return instance;}//私有构造方法private Singleton(){}
}
public class Demo17 {public static void main(String[] args) {//把构造方法设置为私有,之后就无法实例化这个对象了,这能通过刚刚创建的方法//Singleton singleton = new Singleton();Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();//这两个对象其实是一样的System.out.println(s1 == s2);}
}
通过运行结果我们可以验证,上述在main方法中创建的对象是同一个,也就是刚刚在类中创建的那一个对象
这种方法会在类加载时就把对象创建好,如果不使用就会造成资源浪费
懒汉模式
具体实现
- 用static修饰,只声明类型创建实际对象
- 私有构造方法
- 提供外界用来获得对象的方法,设定当第一次调用方法时才创建对象
class SinngletonLazy{//在第一次调用时创建对象(懒汉模式)//只声明一个类型不创建实际对象private static SinngletonLazy instance = null;public static SinngletonLazy getInstance(){//如果是第一次调用,就创建实例对象,否则直接返回对象if(instance == null){instance = new SinngletonLazy();}return instance;}//私有构造方法private SinngletonLazy(){}
}
饿汉模式和懒汉模式的最大区别就是,饿汉模式在类加载时就创建了对象,懒汉模式在第一次调用方法时才创建对象,假如我们要打开一本电子书,饿汉模式是要把整本书都加载完成才可以看,而懒汉模式则是在翻页时才加载下一页,可想而知懒汉模式是更高效的。
懒汉模式(优化)
不过上述实现懒汉模式的方法只在单线程下才适用,如果是在多线程环境下会引起线程安全问题。
这里我来画图解释一下
如果当t1执行到①时,if判定为真,程序将要进到if语句内部,此时t2线程插了进来,由于t1线程还没创建出对象,所以t2线程的if语句也会判定为真,接着t2线程new了一个对象然后返回,最后又回到t1线程这边紧接着又new了一个对象,此时代码就出现问题了。
我们第一时间想到的方法就是直接给方法加上锁,这个方法确实可以解决问题,但是这样的话,代码的并发性就降低了,进而影响到代码的效率,所以我们要换种思路。
可以思考一下,我们的加锁操作只是再第一次创建对象时才需要,所以我们在加锁操作前再加一个if语句来判断当前是否需要加锁,这样就既实现了懒汉模式,又优化了代码的效率
class SinngletonLazy{//在第一次调用时创建对象(懒汉模式)//只声明一个类型不创建实际对象private static SinngletonLazy instance = null;public static SinngletonLazy getInstance(){//如果是第一次调用,就加锁创建对象,否则直接返回对象if(instance == null) {//判断是否需要加锁synchronized (SinngletonLazy.class){if (instance == null) {//判断是否需要new对象instance = new SinngletonLazy();}}}return instance;}//私有构造方法private SinngletonLazy(){}
}
用简洁的话总结一下就是,因为为了防止资源浪费,所以使用一个if条件判断是否需要创建对象,因为在多线程下会出现线程安全的问题,所以要加锁,又因为只需要再第一次创建对象时才需要加锁,所以为了提升效率,再加一层if判断当前是否需要加锁(这两个if本质上没有什么关系,只是刚好判断条件一样而已)
这种方法也叫做Double Check(双重检验) + Lock(加锁)
虽然当前的代码已经很完善了但是还是会有指令重排序的问题
指令重排序
指令重排序也是一种编译器的优化,是编译器为了提高效率,在保证代码逻辑顺序不变的情况下,改变代码的实际顺序。
实际上这里的new操作可以分成三个步骤
- 向内存申请空间
- 在刚刚申请的空间上构造对象
- 把刚刚申请的空间的地址付给instance
但是后面两个步骤对于编译器来说是可以颠倒的,按照123或者132来执行都是可以的,就看那种效率快,在单线程下是没有问题的但是在多线程下就会出现问题
假设有t1,t2线程,t1线程执行到new操作之后先执行1,3,此时编译器已经把刚刚申请的内存地址付给instance,Instance此时已经是一个非空的了,也就是说,此时instance指向一个还没有初始化的非法对象。但是这个时候还没有执行2操作,假如这时t2线程开始执行,判定第一个instance==Null,条件不成立(因为刚刚t1线程已经给instance赋值了),t2线程就直接会返回instance对象。但是此时,t1还没有到内存上构造出对象,T2线程的代码可能就会访问instance里面的属性了,进而就会引起一些bug。
这里可能会有疑问了,刚刚不是给new操作加锁了吗?为什么还会再new操作时插入其他线程?这就是刚刚代码的缺陷之处,我们为了提高效率只是给new操作加锁了,但是此时t2线程只是执行到了第一个if条件,还没有涉及到任何锁操作,就更谈不上阻塞等待了。t2线程此时连第一个if条件都没有进去,就拿着一个还没有初始化的非法对象返回了。
解决方法
不过解决方法也很简单,使用volatile关键字修饰就可以了,volatile可以防止指令重排序,相当于告诉编译器,不要进行代码优化,让它按照本来的顺序执行
此外volatile还有一个作用:保证内存可见性,就是每一时刻线程读取到该变量的值都是内存中最新的那个值(线程每次操作该变量都需要先读取该变量)
最终代码展示:
class SinngletonLazy{//在第一次调用时创建对象(懒汉模式)//只声明一个类型不创建实际对象private static volatile SinngletonLazy instance = null;public static SinngletonLazy getInstance(){//如果是第一次调用,就加锁创建对象,否则直接返回对象if(instance == null) {//判断是否需要加锁synchronized (SinngletonLazy.class){if (instance == null) {//判断是否需要new对象instance = new SinngletonLazy();}}}return instance;}//私有构造方法private SinngletonLazy(){}
}
总结
饿汉模式:在类加载时创建对象,通过方法直接返回该对象,不会出现并发安全问题
懒汉模式:在第一次需要对象是才会创建对象,但会有并发问题,建议使用Double Check(双重检验) + Lock(加锁) 可以很好的解决问题
为了在多线程环境下防止,疑问指令重排序而导致代码出现问题,要使用volatile修饰对象
以上就是博主对单例模式知识的分享,在之后的博客中会陆续分享有关线程的其他知识,如果有不懂的或者有其他见解的欢迎在下方评论或者私信博主,也希望多多支持博主之后和博客!!🥰🥰
下一篇博客博主将分享有关阻塞队列等知识,还希望多多支持一下!!!😊
相关文章:

《javaEE篇》--单例模式详解
目录 单例模式 饿汉模式 懒汉模式 懒汉模式(优化) 指令重排序 总结 单例模式 单例模式属于一种设计模式,设计模式就好比是一种固定代码套路类似于棋谱,是由前人总结并且记录下来我们可以直接使用的代码设计思路。 单例模式就是,在有…...

Java核心 - Lambda表达式详解与应用示例
作者:逍遥Sean 简介:一个主修Java的Web网站\游戏服务器后端开发者 主页:https://blog.csdn.net/Ureliable 觉得博主文章不错的话,可以三连支持一下~ 如有疑问和建议,请私信或评论留言! 前言 Lambda表达式是…...

算法通关:006_1二分查找
二分查找 查找一个数组里面是否存在num主要代码运行结果 详细写法自动生成数组和num,利用对数器查看二分代码是否正确 查找一个数组里面是否存在num 主要代码 /*** Author: ggdpzhk* CreateTime: 2024-07-27*/ public class cg {//二分查找public static boolean …...

总结一些vue3小知识3
总结一些vue3小知识1:http://t.csdnimg.cn/C5vER 总结一些vue3小知识2:http://t.csdnimg.cn/sscid 1.限制时间选择器只能选择后面的日期 说明:disabled-date属性是一个用来判断该日期是否被禁用的函数,接受一个 Date 对象作为参…...

JAVAWeb实战(前端篇)
项目实战一 0.项目结构 1.创建vue3项目,并导入所需的依赖 npm install vue-router npm install axios npm install pinia npm install vue 2.定义路由,axios,pinia相关的对象 文件(.js) 2.1路由(.js) import {cre…...

axios请求大全
本文讲解axios封装方式以及针对各种后台接口的请求方式 axios的介绍和基础配置可以看这个文档: 起步 | Axios中文文档 | Axios中文网 axios的封装 axios封装的重点有三个,一是设置全局config,比如请求的基础路径,超时时间等,第二点是在每次…...

C# 简单的单元测试
文章目录 前言参考文档新建控制台项目新建测试项目添加引用添加测试方法测试结果(有错误)测试结果,通过正规的方法抛出异常 总结 前言 听说复杂的项目最好都要单元测试一下。我这里也试试单元测试这个功能。到时候调试起来也方便。 参考文档 C# 单元测试…...

Linux中Mysql5.7主从架构(一主多从)配置教程
🏡作者主页:点击! 🐧Linux基础知识(初学):点击! 🐧Linux高级管理防护和群集专栏:点击! 🔐Linux中firewalld防火墙:点击! ⏰️创作…...

BACnet物联网关BL103:Modbus协议转BACnet/MSTP
随着物联网技术在楼宇自动化与暖通控制系统中的迅猛发展,构建一种既经济高效又高度可靠的协议转换物联网关成为了不可或缺的核心硬件组件。在此背景下,我们钡铼特别推荐一款主流的BAS(楼宇自动化系统)与BACnet物联网关——BL103&a…...

Go 语言条件变量 Cond
1.Cond 的使用方法 Go 标准库提供 Cond 同步原语的目的是为等待/通知场景下的并发操作提供支持。Cond 通常用于等待某个条件的一组 goroutine,当条件变为 true 时,其中一个或者所有的 goroutine 会被唤醒执行。 Cond 与某个条件相关,这个条件需要一组 goroutine 协作达到。当这…...

PostgreSQL 中如何重置序列值:将自增 ID 设定为特定值开始
我是从excel中将数据导入,然后再通过sql插入数据,就报错。 需要设置自增ID开始值 1、确定序列名称: 首先,需要找到与的增字段相关的序列名称。假设表名是 my_table 和自增字段是 id,可以使用以下查询来获取序列名称…...

Unity 之 【Android Unity 共享纹理】之 Android 共享图片给 Unity 显示
Unity 之 【Android Unity 共享纹理】之 Android 共享图片给 Unity 显示 目录 Unity 之 【Android Unity 共享纹理】之 Android 共享图片给 Unity 显示 一、简单介绍 二、共享纹理 1、共享纹理的原理 2、共享纹理涉及到的关键知识点 3、什么可以实现共享 不能实现共享…...

Go语言的数据结构
数据结构 数组 支持多维数组,属于值类型,支持range遍历 例子:随机生成长度为10整数数组 package main import ("fmt""math/rand" ) // 赋值 随机获取100以内的整数 func RandomArrays() {var array [10]int //声明var…...

python_在sqlite中创建表并写入表头
python_在sqlite中创建表并写入表头 import sqlite3def write_title_to_sqlite(tableName,titleList,dataTypeGroupsList,database_path):conn sqlite3.connect(database_path)# 创建游标cursor conn.cursor()#MEMO 长文本#create_table_bodycreate_table_body "序号 …...

1.c#(winform)编程环境安装
目录 安装vs创建应用帮助查看器安装与使用( msdn) 安装vs 安装什么版本看个人心情,或者公司开发需求需要 而本栏全程使用vs2022进行开发c#,着重讲解winform桌面应用开发 使用***.net framework***开发 那先去官网安装企业版的vs…...

图中的最短环
2608. 图中的最短环 现有一个含 n 个顶点的 双向 图,每个顶点按从 0 到 n - 1 标记。图中的边由二维整数数组 edges 表示,其中 edges[i] [ui, vi] 表示顶点 ui 和 vi 之间存在一条边。每对顶点最多通过一条边连接,并且不存在与自身相连的顶…...

安装依赖 npm install idealTree:lib: sill idealTree buildDeps 卡着不动
我一直怀疑是网络问题,因为等了很久也能安装成功,就是时间比较长,直到现在完全受不了了,决定好好整治下这个问题! 1、执行命令 npm config get userconfig 查看配置文件所在位置,将其删除。 2、执行 n…...

LLMs之Llama 3.1:Llama 3.1的简介、安装和使用方法、案例应用之详细攻略
LLMs之Llama 3.1:Llama 3.1的简介、安装和使用方法、案例应用之详细攻略 导读:2024年7月23日,Meta重磅推出Llama 3.1。本篇文章主要提到了Meta推出的Llama 3.1自然语言生成模型。 背景和痛点 >> 过去开源的大型语言模型在能力和性能上一…...

如何实现一个大模型在回答问题时同时提供相关内容链接
通义生成 为了让大模型在回答问题时能够提供相关内容链接,通常采用的方法是结合检索增强生成(Retrieval-Augmented Generation, RAG)的技术。这种方法可以让大模型在生成答案的同时,从外部知识源中检索相关信息,并将这…...

<数据集>玉米地杂草识别数据集<目标检测>
数据集格式:VOCYOLO格式 图片数量:9900张 标注数量(xml文件个数):9900 标注数量(txt文件个数):9900 标注类别数:2 标注类别名称:[Maize, Weed] 序号类别名称图片数框数1Maize8439125142Weed959231048…...

vue3中动态添加form表单校验
<template><div><div v-for"(formData, index) in forms" :key"index"><u-form :model"formData" :rules"rules" ref"formRefs"><u-form-item label"用户名" prop"username"…...

Java面试八股之什么是声明式事务管理,spring怎么实现声明式事务管理?
什么是声明式事务管理,spring怎么实现声明式事务管理? 声明式事务管理是一种编程范式,它允许开发人员通过声明性的配置或注解,而不是硬编码事务处理逻辑,来指定哪些方法或类应该在其上下文中执行事务。这种方法将事务…...

springboot 缓存预热的几种方案
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。 这里我给大家总结几个缓存预热的方案。 方案1:使用启动监听事件实现缓存预热 可以使用 ApplicationListener 监听 ContextRefreshed…...

谷粒商城实战笔记-62-商品服务-API-品牌管理-OSS整合测试
文章目录 一,Java中上传文件到阿里云OSS1,整合阿里云OSS2,测试上传文件 二,Java中整合阿里云OSS服务指南引言准备工作1. 注册阿里云账号2. 获取Access Key3. 添加依赖 实现OSS客户端1. 初始化OSSClient2. 创建Bucket3. 上传文件4.…...

linux c 递归锁的介绍
递归锁的递归特性确实只是对于持有锁的线程。当一个线程获取了递归锁后,它可以多次重复获取该锁,而不会导致自身阻塞或死锁。这是递归锁的重要特点,它允许同一个线程在已经持有锁的情况下,再次获取相同的锁。 然而,对…...

React好用的组件库有哪些
React好用的组件库有很多,它们各自具有不同的特点和优势,适用于不同的开发场景和需求。以下是一些受欢迎的React组件库及其特点: Material-UI(现更名为MUI) 特点:这是一个开源的React组件库,实…...

简单快捷!Yarn的安装与使用指南
Yarn 是由 Facebook (现 Meta) 开发的包管理工具。 今天,我将介绍如何使用 Yarn。 目录 Yarn 的官方网站 关于安装 版本确认 开始一个新项目(创建 package.json 文件) 安装软件包 升级包 运行脚本 执行包的命令 卸载包 总结 Yarn 的…...

【Django】前端技术-网页样式表CSS
文章目录 一、申明规则CSS的导入方式行内样式内部样式外部样式 二、CSS的选择器1. 基本选择器标签选择器: 选择一类标签 标签{}类选择器 class: 选择所有class属性一致的表情,跨标签.类名{}ID选择器:全局唯一 #id名{} 2.层次选择器…...

openssl req 详解
一、openssl req 该命令用于创建和处理PKCS#10格式的证书请求(certificate requests CSRs),也可以用来创建自签名证书( self-signed certificates)来当作根证书(root CAs)使用 -new 该选项用来…...

mysql各种锁总结
mysql全局锁 读锁(共享锁) 阻止其他用户更新,但允许他们读取数据。 写锁(排他锁) 阻止其他用户读取和更新数据。 全局锁场景:进行数据库备份 数据库备份 背景:备份数据肯定要保证数据一致…...