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

JavaEE 初阶(10)——多线程8之“单例模式”

目录

一. 设计模式 

二. 单例模式 

2.1 饿汉模式

2.2 懒汉模式

a. 加锁synchronized

b. 双重if判定

c. volatile关键字(双重检查锁定)


一. 设计模式 

   设计模式是在软件工程中解决常见问题的经典解决方案。针对一些特定场景给出的一些比较好的解决方案,只要按照设计模式来写代码,就可以使代码不会太差(保证了代码的下限)。

   设计模式比较适用于C++,Java,C#,但是对于 Python 或 Erlang 这些语言,这里的很多设计模式都是不适用的。设计模式适合具有一定的编程经验之后再去学习,如果缺少编程经验,会比较难以理解。

二. 单例模式 

   单例模式 是Java中最简单的设计模式之一,它确保一个类只有一个实例,并提供一个全局访问点。一个Java程序中,某个类要求只有唯一一个实例,适合使用单例模式(单例模式前提是“一个进程中”,如果有多个Java进程,自然每个进程中都可以有一个实例了)

在Java中,实现单例模式主要有两种方式:“饿汉模式”“懒汉模式”

单例模式三部曲

  • static 修饰 instance 成员变量(类变量)
  • 构造方法私有
  • 静态全局访问点
2.1 饿汉模式

    在饿汉模式中,单例对象在类加载时就被立即初始化。这意味着类加载完成后,单例对象就已经创建好了,不管你是否需要它。

public class SingletonEager {//静态实例变量private static SingletonEager instance = new SingletonEager();//构造方法私有private SingletonEager(){}//全局访问点(每次需要通过getInstance来获取实例的)public static SingletonEager getInstance(){return instance;}
}

饿汉模式中的 “饿” 的意思是 “迫切”(eager),即在类被加载的时候,就会创建出这个单例的实例。

优点

  • 简单易实现,类加载时就完成了实例化,避免了线程安全问题

缺点

  • 如果自始至终未使用过这个实例,则会造成内存浪费
2.2 懒汉模式

    在懒汉模式中,单例对象在第一次使用时才进行初始化。这种方式可以延迟对象的创建,只有在实际需要时才会创建对象。

public class SingletonLazy {//静态变量声明时不初始化private static SingletonLazy instance;//构造方法私有private SingletonLazy(){}//初次使用会进行初始化public static SingletonLazy getInstance(){if(instance == null){instance = new SingletonLazy();}return instance;}
}

懒汉模式中的 “懒” 的意思是推迟了创建实例的时机,首次调用getInstance,才会创建实例
计算机中,谈到的"懒"是褒义词,意思是效率会更高,能不搞就不搞,很多时候,就可以把这部分开销就省下了

相比饿汉模式,懒汉模式的效率会更高一些。

比如:有一个编辑器,打开一个非常大(1G)的文本文档

  • 一启动,就把所有的文本内容都读取到内存中,然后再显示到界面上 [饿汉]
  • 启动之后,只加载一小部分数据(一个屏幕能显示的最大数据),随着用户进行翻页操作,再按需加载剩下的内容  [懒汉]

很明显,懒汉模式会造成巨大的开销,而懒汉模式开销更小~~


* 但是,如果在多线程下,调用getInstance 是否会有线程安全问题呢?——  多个线程针对一个变量进行修改:如果只是读取,则没有问题;先判定,再修改的这种代码模式,属于典型的线程不安全代码,判定和修改之间可能涉及到线程的切换。

   当线程A初次调用 getInstance,判断 instance 为null,会执行创建实例(但还未执行就被调度走了的情况下);此时调度线程B,线程B刚好执行 getInstance,此时判断 instance 仍然为 null,又会执行一次创建实例。

   这样,同一个进程中就会执行两次创建实例的操作。虽然第二次创建,覆盖了 instance 的值,使得第一次创建的实例,没有引用指向,很快就会被垃圾回收机制给消除掉(instance 的引用仍是唯一的),但这样多次new操作已经造成了线程安全问题~~

   因此,需要对判断和初始化这两个操作打包成原子的.....


a. 加锁synchronized
public class SingletonLazy {private static SingletonLazy instance;private SingletonLazy(){}public static SingletonLazy getInstance(){//将判断和初始化操作打包成原子操作,加锁synchronized (SingletonLazy.class){if(instance == null){instance = new SingletonLazy();}}return instance;}
}

   注意此处的 return 操作不用加到同步代码块中。因为,无论 return 操作是在本线程还是其他线程,return的值都是Instance内存中最新的值!!


   加锁之后,确实解决了线程安全问题,但是加锁同样也可能带来阻塞.....

如果上述代码已经 new 完对象了,if 的判断分支再也进不去了,后续的代码 都是单纯的 读操作,已经没有线程安全问题了。但是以后只要调用 getInstance ,都会触发加锁操作,但在第一次初始化后,其实已经没有必要加锁了。加锁以后,还会产生阻塞,影响到性能。

   因此,针对这个问题,还要进一步改进——通过条件判断,在应该加锁的时候,才加锁;不需要加锁的时候,直接跳过加锁


b. 双重if判定
public class SingletonLazy {private static SingletonLazy instance;private SingletonLazy(){}public static SingletonLazy getInstance(){//再加一个if判断,instance不为null,直接返回值即可,不用加锁阻塞if(instance == null){synchronized (SingletonLazy.class){if(instance == null){instance = new SingletonLazy();}}}return instance;}
}

以往写的代码中,从未遇到过同样的if 连着写:

曾经的单线程代码中,这样的写法是毫无意义的,两个条件值一定相同。

但是在多线程代码中,任意两个代码之间,都可能穿插其他线程的逻辑。synchronized会使代码出现阻塞,一旦阻塞之后,啥时候恢复执行,是无法预知的。在这个过程中,很可能其他线程就把这个值给修改了~~

这俩条件,只是恰好写法一样,实际上,作用是完全不同的~~

外面的 if 判定 是否要加锁;里面的 if 是判定 是否要创建对象

只不过巧了!在这个代码中,通过同样的方式,完成上述两种判定的~~


但是,这个代码可能还会因为指令重排序(编译器优化),引起线程安全问题如果是单线程代码,编译器都能准确的进行判断;如果是多线程代码,编译器也是可能出现误判

 在创建实例 instance = new SingletonLazy () 时,这行代码实际上可以分解为以下三个操作:

     1. 分配内存空间

     2. 初始化对象

     3. 将对象引用指向分配的内存空间(执行后instance值不再为null)

在没有volatile关键字的情况下,编译器和处理器可能会对上述操作进行重排~~

例如:

     a. 分配内存空间

     b. 将对象引用指向分配的内存空间(执行后instance值不再为null)

     c. 初始化对象

如果发生了这种重排,假设 线程A 正在执行 instance = new SingletonLazy (),而 线程B 几乎同时调用 getInstance 方法,可能会发生以下情况:

  • 线程A 执行了 a 和 b,此时 instance 已经不为 null,但是对象还没有被初始化
  • 线程B 进入 getInstance 方法,执行第1次检查,发现 instance 不为 null,因此直接返回 instance
  • 线程B 在使用未完全初始化的 instance 时,可能会遇到空指针异常或其他错误。

    在Java中,所有的对象引用默认值都是null,如果声明了一个对象引用但没有进行初始化,那么试图访问这个引用的任何方法或属性都会抛出空指针异常

   此处相当于引用已经不为null了,但是没有为 instance 中的属性和方法进行初始化,因此使用 instance 的时候会抛出空指针异常。

   因此,为了解决这个问题,要对 instance 变量用 volatile关键字 修饰

* “指令重排序”的详细讲解在 JavaEE初阶(6)

* volatile 关键字的特性(保证“可见性”与“有序性”,不保证“原子性”)详细讲解在JavaEE初阶(8)

c. volatile关键字(双重检查锁定)
public class SingletonLazy {//volatile关键字修饰instanceprivate static volatile SingletonLazy instance;private SingletonLazy(){}public static SingletonLazy getInstance(){if(instance == null){synchronized (SingletonLazy.class){if(instance == null){instance = new SingletonLazy();}}}return instance;}
}

  volatile关键字 的作用之一就是禁止对 volatile变量 前后的操作进行指令重排。这样,当线程A创建 SingletonLazy 实例时,即使发生了指令重排,其他线程在读取 instance 变量时也能保证看到的是完全初始化后的对象。volatile 不仅保证了变量的可见性,还防止了指令重排可能带来的问题。

   换个角度看,加上volatile之后,也能禁止对 instance 赋值操作 插入到其他操作之间。


总结: 

设计模式:软件开发中,针对一些特定套路,给出的特定解决方案

单例模式

  • 饿汉模式:程序启动,类加载的时候,就会创建实例——不涉及线程安全问题
  • 懒汉模式:在程序第一次使用这个实例的时候,才会创建实例——涉及到线程安全问题

             a. 加锁把if判定和new赋值操作,打包成原子操作

             b. 双重 if 判定,解决加锁的性能问题

             c. volatile关键字解决指令重排问题

相关文章:

JavaEE 初阶(10)——多线程8之“单例模式”

目录 一. 设计模式 二. 单例模式 2.1 饿汉模式 2.2 懒汉模式 a. 加锁synchronized b. 双重if判定 c. volatile关键字(双重检查锁定) 一. 设计模式 设计模式是在软件工程中解决常见问题的经典解决方案。针对一些特定场景给出的一些比较好的解决…...

Javascript常见设计模式

JS设计模式学习【待吸收】-CSDN博客 JavaScript 中的设计模式是用来解决常见问题的最佳实践方案。这些模式有助于创建可重用、易于理解和维护的代码。下面列出了一些常见的 JavaScript 设计模式及其代码示例。 1. 单例模式(Singleton) 单例模式确保一…...

JavaFX布局-SplitPane

JavaFX布局-SplitPane 常用属性orientationpaddingdividerPositionsdisable 实现方式Java实现fxml实现 一个拆分至少两个区域的容器支持水平、垂直布局可以拖动区域的大小初始化大小通过比例设置[0,1] 常用属性 orientation 排列方式,Orientation.VERTICAL、Orien…...

2.MySQL库的操作

创建数据库 创建数据库的代码: CREATE DATABASE [IF NOT EXISTS] db_name [create_specification [,create_specification] ...];​create_specification:[DEFAULT] CHARACTER SET charset_name[DEFAULT] COLLATE collation_name 说明: 大写的表示关键…...

如何学习计算机

不要只盯着计算机语言学习,你现在已经学习了C语言和Java,暑假又规划学习Python,最后你掌握的就是计算机语言包而已。 2. 建议你找一门想要深挖的语言,沿着这个方向继续往后学习知识就行。计算机语言是学不完的,而未来就…...

Spring MVC 快速入门指南及实战演示

1、SpringMVC简介 1.1 背景 Servlet属于web层开发技术,技术特点: 1. 每个请求都需要创建一个Servlet进行处理 2. 创建Servlet存在重复操作 3. 代码灵活性低,开发效率低 是否有技术方案可以解决以上问题? 1.2 SpringMVC概述 Sp…...

在线测评系统(未完结)

文章目录 注意!!!1、多模块开发(后端)(1).Maven依赖(2)swagger配置 2、判题机开发(1)docker 前言:大二刚开始接手了本学院的oj,并管理了一段时间,后来老师给我…...

Python 爬虫项目实战(一):破解网易云 VIP 免费下载付费歌曲

前言 网络爬虫(Web Crawler),也称为网页蜘蛛(Web Spider)或网页机器人(Web Bot),是一种按照既定规则自动浏览网络并提取信息的程序。爬虫的主要用途包括数据采集、网络索引、内容抓…...

PTA 6-7 统计某类完全平方数

6-7 统计某类完全平方数(20分) 本题要求实现一个函数,判断任一给定整数N是否满足条件:它是完全平方数,又至少有两位数字相同,如144、676等。 函数接口定义: int IsTheNumber ( const int N );…...

PyFilesystem2 - Python 操作文件系统

文章目录 一、关于 PyFilesystem2二、安装三、快速使用四、指南为什么要使用 PyFilesystem ?打开文件系统树打印关闭目录信息子目录处理文件遍历 WalkingGlobbing移动和复制 五、概念路径系统路径沙盒错误 六、资源信息信息对象命名空间基本命名空间细节命名空间访问…...

Bug小记:关于servlet后端渲染界面时出现的问题小记1P

问题1: 问题描述: int delete(Integer Sno);后端在该方法调用时传入参数 req.getParameter("Sno")报错参数应该为Integer类型问题分析:后端通过请求获取到的前端数据都是字符串类型,需要手动转换参数类型 解决方法&a…...

智慧水务项目(二)django(drf)+angular 18 创建通用model,并对orm常用字段进行说明

一、说明 上一篇文章建立一个最简单的项目,现在我们建立一个公共模型,抽取公共字段,以便于后续模块继承,过程之中会对orm常用字段进行说明,用到的介绍一下 二、创建一个db.py 目录如下图 1、代码 from importlib im…...

<数据集>人员摔倒识别数据集<目标检测>

数据集格式:VOCYOLO格式 图片数量:8605张 标注数量(xml文件个数):8605 标注数量(txt文件个数):8605 标注类别数:1 标注类别名称:[fall] 序号类别名称图片数框数1fall860512275 使用标注工具&#xf…...

npm install 报错 ‘proxy‘ config is set properly. See: ‘npm help config‘

解决 参考链接:npm install 报错 ‘proxy‘ config is set properly. See: ‘npm help config‘-阿里云开发者社区 (aliyun.com)...

爬虫问题---ChromeDriver的安装和使用

一、安装 1.查看chrome的版本 在浏览器里面输入 chrome://version/ 回车查看浏览器版本 Chrome的版本要和ChromeDriver的版本对应,否则会出现版本问题。 2.ChromeDriver的版本选择 114之前的版本:https://chromedriver.storage.googleapis.com/index.ht…...

Spring的配置类分为Full和Lite两种模式

Spring的配置类分为Full和Lite两种模式 首先查看 Configuration 注解的源码, 如下所示: Target({ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Documented Component public interface Configuration {AliasFor(annotation Component.class)String value() defau…...

探索Perl的代码生成艺术:利用编译器后端释放潜能

探索Perl的代码生成艺术:利用编译器后端释放潜能 Perl,作为一种解释型语言,通常不通过编译器后端直接生成机器代码。然而,通过一些高级技术,Perl 程序员可以利用编译器后端来生成代码,从而提高性能或实现特…...

21 B端产品经理之技术常识(1)

产品经理需要掌握一些基本的技术知识。 了解公司前端与后端 前端 前端开发:创建WEB页面或APP等前端界面呈现给用户的过程,即前端负责用户界面交互。 前端技能: HTML:一种标记语言,能够实现Web页面并在浏览器中显示。…...

数据结构基础详解(C语言):单链表_定义_初始化_插入_删除_查找_建立操作_纯c语言代码注释讲解

单链表理论知识详解 文章目录 单链表理论知识详解1.单链表的定义2.单链表的初始化3.单链表的插入和删除3.1 单链表的插入3.1.1 按位序插入3.1.2 在指定结点的前后插入一.后插操作二.前插操作 4.单链表的删除4.1 按位序删除4.2 指定结点的删除 5.单链表的查找5.1 按位序查找5.2 …...

【智能时代的创新工具】LangChain快速入门指南:轻松掌握语言模型的集成与运用

一、LangChain:连接语言模型与现实世界的桥梁 1.1 LangChain的定义与重要性 LangChain是一个开源的Python库,它旨在为开发人员提供一种简便的方式来集成和运用语言模型。它不仅仅是一个简单的API调用工具,而是一个具有丰富功能的框架&#x…...

文献阅读:细胞分辨率全脑图谱的交互式框架

文献介绍 文献题目: An interactive framework for whole-brain maps at cellular resolution 研究团队: Daniel Frth(瑞典卡罗林斯卡学院)、Konstantinos Meletis(瑞典卡罗林斯卡学院) 发表时间&#xff…...

YAML基础语言深度解析

引言 YAML(YAML Aint Markup Language,即YAML不是一种标记语言)是一种直观、易于阅读的数据序列化格式,常用于配置文件、数据交换和程序间的通信。其设计目标是易于人类阅读和编写,同时也便于机器解析和生成。在本文中…...

xcode使用

1. 界面 1.1. Build Settings,Build Phases和Build Rules三个设置项 Build Settings(编译设置): 每个选项由标题(Title)和定义(Definition)组成。这里主要定义了Xcode在编译项目时的一些具体配置 Build Phases(编译资源):用于指定编译过程中项目所链接的原文件,依赖对象,库…...

OV2640引脚的定义(OV2640 FPC模组规格书(接口线序))

OV2640是一款由Omni Vision公司生产的1/4寸CMOS UXGA(1632x1222)图像传感器。这款传感器以其小巧的体积、低工作电压和强大的功能而著称,它集成了单片UXGA摄像头和影像处理器,能够通过SCCB总线控制输出各种分辨率的8/10位影像数据…...

CTFSHOW 萌新 web10 解题思路和方法(passthru执行命令)

点击题目链接,分析页面代码。发现代码中过滤了system、exec 函数,这意味着我们不能通过system(cmd命令)、exec(cmd命令)的方式运行命令。 在命令执行中,常用的命令执行函数有: system(cmd_code);exec(cmd_…...

深入Java数据库连接和JDBC

引言 Java数据库连接(JDBC)是Java语言中用于执行SQL语句的标准API。通过JDBC,开发者可以方便地与关系型数据库进行交互。然而,直接使用JDBC API面临着数据库连接管理复杂、性能瓶颈等问题。数据库连接池作为一种解决方案,可以有效地管理数据库连接,提高应用程序的性能。…...

灰狼优化算法(GWO)与长短期记忆网络(LSTM)结合的预测模型(GWO-LSTM)及其Python和MATLAB实现

#### 一、背景 在现代数据科学和人工智能领域,预测模型的准确性和效率是研究者和工程师不断追求的目标,尤其是在时间序列预测、金融市场分析、气象预测等领域。长短期记忆(LSTM)网络是一种解决传统递归神经网络(RNN&a…...

电路板热仿真覆铜率,功率,结温,热阻率信息计算获取方法总结

🏡《电子元器件学习目录》 目录 1,概述2,覆铜率3,功率4,器件尺寸5,结温6,热阻1,概述 电路板热仿真操作是一个复杂且细致的过程,旨在评估和优化电路板内部的热分布及温度变化,以确保电子元件的可靠性和性能。本文简述在进行电路板的热仿真时,元器件热信息的计算方法…...

C#中多线程编程中的同步、异步、串行、并行及并发及死锁

在C#中,多线程编程是一个强大的功能,它允许程序同时执行多个任务。然而,这也带来了复杂性,特别是在处理同步、异步、串行、并行、并发以及死锁等问题时。下面我将详细解释这些概念,并给出一些C#中的示例和注意事项。 …...

【Lampiao靶场渗透】

文章目录 一、IP地址获取 二、信息收集 三、破解SSH密码 四、漏洞利用 五、提权 一、IP地址获取 netdiscover -i eth0 Arp-scan -l Nmap -sP 192.168.78.0/24 靶机地址:192.168.78.177 Kali地址:192.168.78.128 二、信息收集 nmap -sV -p- 192.…...

建设微信营销网站制作/杭州明开seo

基于三向张量分解(factorization of a three-way tensor)的关系学习方法 我们的方法与其他张量方法不同,我们的方法能够可以通过模型的潜在组件进行集体学习并提供一种计算分解的有效的算法。 我们证实我们关于模型集体学习能力的理论考虑通过在新数据集和实体解析中…...

小说网站怎么做防采集/本周国内新闻

近日,通用电气公司(GE)和中国大唐集团公司签署协议,在北京组建北京国际电力数据监测诊断中心,向大唐集团提供电力数字化解决方案,开启“互联网”、云计算、和大数据时代背景下的强强合作。本次合作是大唐集…...

外贸网站建设行业发展情况/百度竞价托管代运营

记得在大学接触的初级编程语言丛书都会推荐先用伪代码来理清程序逻辑再用相应的语法和变量来实现程序。那时总认为那是一种很低级的编程策略,耗费精力,浪费时间。于是在以后早期的程序生涯中,从未用过先写伪代码的形式写过一次程序。可是去年…...

柳州市住房和城乡建设局网站/南宁seo网络优化公司

3.共享文件夹路径 /mnt/hgfs/winshare/posted on 2016-07-30 19:46 Schling 阅读(...) 评论(...) 编辑 收藏 转载于:https://www.cnblogs.com/schling/p/5721590.html...

招聘网站建设与开发要求/正规seo需要多少钱

【单选题】如果要回滚一个事务,则要使用( )语句【单选题】下列描述正确的是( )【单选题】以下哪项不属于数据模型【单选题】与文件系统阶段相比,关系数据库技术的数据管理方式具有许多特点,但不包括【填空题】输入矩阵 A[3,2,5;8,-5,7;-1,4,9] ,使用全下标方式用 A(2,2) 取出元…...

怎样在商务部网站做备案/游戏优化大师官网

rqt_publisher 提供了一个 GUI 插件,用于发布具有固定或在计算范围内的任意消息。 开始 在命令行输入以下命令启动插件: rosrun rqt_publisher rqt_publisher 如果提示未找到rqt_publisher包,则运行以下命令进行安装: sudo a…...