【设计模式】单例设计模式详解(包含并发、JVM)

文章目录

  • 1、背景
  • 2、单例模式
  • 3、代码实现
    • 1、第一种实现(饿汉式)
    • 为什么属性都是static的?
    • 2、第二种实现(懒汉式,线程不安全)
    • 3、第三种实现(懒汉式,线程安全)
    • 4、第四种实现(懒汉式,双重校验锁DCL)
      • getSingleton里为什么会有两个if判空?
      • singleton为什么被volatile修饰

1、背景

在软件开发中,经常需要某些类只能有唯一的实例,比如数据库连接。如何才能保证整个应用中只有一个唯一实例?如果靠人为制定的协定来约束,显然不能很好的保证这一点。如果要从语法上约束,在面向对象里面,什么地方能够约束实例的创建?
显然,只有构造函数类实例的创建相关。那么如何才能让构造函数阻止类实例的创建,使其只有一个唯一实例?让构造函数的修饰为私有

2、单例模式

单例模式是设计模式中最简单的形式之一。这一模式的目的是使得类的一个对象成为系统中的唯一实例。要实现这一点,可以从客户端对其进行实例化开始。因此需要用一种只允许生成对象类的唯一实例的机制,“阻止”所有想要生成对象的访问。

3、代码实现

1、第一种实现(饿汉式)

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

饿汉式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可。类在加载时会在JVM的堆内存中创建一个Singleton对象,当类被卸载时,Singleton对象也随之消亡了(没有被引用)。

它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

为什么属性都是static的?

为了使其只有一个唯一实例,我们将无参构造方法设置成静态的,其他的类就没有办法直接通过new来构建该类的对象。无法通过new来构建类对象,那就只能通过调用类的静态方法getInstance这个唯一进出口来获得对象。由于静态方法里只能使用静态(static)属性,所以instance被修饰为静态的(static)。

2、第二种实现(懒汉式,线程不安全)

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

这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

为什么不支持多线程?
假如现在有两个线程,一个线程a,一个线程b,线程a被操作系统选中,分配时间片,去调用Singleton.getInstance(),判断instance== null,为true,进入代码块,然后时间片用完,切换到线程b执行,线程b也调用Singleton.getInstance(),当运行到instance==null时,因为线程a上次判断完instance为null,就结束了,所以此时instance还是null,于是线程b执行instance=new Singleton(),并返回instance。线程b时间片用完,让出cpu,线程a被选中,从instance=new Singleton开始执行, 于是又在堆中创建了一个Singleton对象实例,并返回。所以严格意义上说它并不算单例模式。

3、第三种实现(懒汉式,线程安全)

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

这样就规避了两个线程同时创建Singleton对象的风险,但是引来另外一个问题:每次去获取对象都需要先获取锁,并发性能非常地差,极端情况下,可能会出现卡顿现象。因为在getInstance方法上加了synchronized锁,调用该方法的线程需要排队调用。(一个线程获得了Singleton类锁后,在该线程执行完getInstance()方法前,其他线程要么阻塞,要么自旋,无法执行getInstance方法)

4、第四种实现(懒汉式,双重校验锁DCL)

public class Singleton {  private volatile static Singleton singleton;  //1private Singleton (){}  //2public static Singleton getSingleton() { //3 if (singleton == null) {  //4synchronized (Singleton.class) { //5 if (singleton == null) {  //6singleton = new Singleton(); //7 }  }  }  return singleton;  }  
}

第四种实现对于第三种实现进行了优化,如果没有实例化对象则加锁创建,如果已经实例化了,则不需要加锁,直接获取实例,这种优化提高了高并发下的性能。

getSingleton里为什么会有两个if判空?

假设现在有3个线程:线程A、线程B、线程C。线程A执行第一个if判断为空后,时间片用完让出cpu,切换线程。线程B执行第一个if判断为空后,加锁,加锁后恰好时间片用完,让出cpu。切换到线程C ,判断完第一个if后,因为B没有释放锁,等待时间片结束(cpu空转),切换线程。当再次切换到线程B后,进入同步代码块,先判断singleton是不是null,是null,创建对象,释放锁,返回对象。当执行完第一个if判断后阻塞在加锁的线程 ,抢到锁后,继续执行剩下的代码,在第二个if判断时,因为第一个线程已经创建了对象,则跳过,退出同步代码块,释放锁,得到线程B创建的实例对象。后面再来的线程在第一个if判断返回false后,直接返回线程B创建好的实例对象。

由此可见:第一个if是为了验证是否已经创建了对象,该判断是为了避免不必要的同步,第二个if是为了避免重复创建单例,是给第二个以及后面持有锁的线程准备的。

singleton为什么被volatile修饰

什么是指令重排序:JVM在保证最终结果正确的情况下,可以不按照程序编码的顺序执行语句,尽可能提高程序的性能。

new对象操作在指令层面不是一个原子操作,分为三步:

  1. 为singleton分配内存空间M
  2. singleton初始化
  3. 将singleton指向分配好的内存空间M

在执行new操作时,2、3步可能发生指令重排,会发生下面这种情况:申请空间后,存入地址,但singleton对象还未初始化,切换线程。当新来的线程进行第一个if判断时,因为方法区中singleton里面是有堆中地址的,判断不为null,当return时,因为申请的空间中没有数据,则报空指针异常。
如下图:
在这里插入图片描述

使用volatile关键字可以防止指令重排序。使用volatile关键字修饰的变量,可以保证其指令执行的顺序与程序指明的顺序一致,不会发生顺序变换。使用volatile关键字修饰的变量,可以保证其内存可见性,即每一时刻线程读取到该变量的值都是内存中最新的那个值,线程每次操作该变量都需要先读取该变量。

我在这里对堆、方法区、Java虚拟机栈进行了详解😄😄😄😄

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

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

相关文章

day38-Mobile Tab Navigation(手机tab栏导航切换)

50 天学习 50 个项目 - HTMLCSS and JavaScript day38-Mobile Tab Navigation&#xff08;手机tab栏导航切换&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"…

3ds MAX 洗菜池

在家居中我们显然离不开这个对吧 首先绘制一个长方体作为基础 注意设置长宽高的网格大小&#xff0c;方便后续调整 俯视图网格线如下&#xff1a; 长方形变换为可编辑网络&#xff0c;并在【多边形】界面选择底面的所有多边形&#xff0c;按delete删除&#xff0c;形成一个壳体…

Github上方导航栏介绍

Code Watch&#xff1a;相当于关注&#xff0c;到时候这个项目又有什么操作&#xff0c;就会以通知的形式提醒你。 Fork&#xff1a;也就是把这个项目拉到你的仓库里&#xff0c;之后你可以对该代码进行修改&#xff0c;之后你可以发起Pull Request&#xff0c;简称PR&#xf…

vulnhub靶场之CengBox3

1.信息收集 输入命令&#xff1a;netdiscover -i eth0 -r 192.168.239.0 &#xff0c;发现181机器存活 输入命令nmap -p- -sV -O -Pn -A 192.168.239.181 &#xff0c;进行端口探测&#xff0c;发现存在22、80、443端口&#xff0c;还发现存在域名ceng-company.vm。 将域名c…

了解Unity编辑器之组件篇Tilemap(五)

Tilemap&#xff1a;用于创建和编辑2D网格地图的工具。Tilemap的主要作用是简化2D游戏中地图的创建、编辑和渲染过程。以下是一些Tilemap的主要用途&#xff1a; 2D地图绘制&#xff1a;Tilemap提供了一个可视化的编辑器界面&#xff0c;可以快速绘制2D地图&#xff0c;例如迷…

python实现逻辑回归-清风数学建模-二分类水果数据

所用数据 &#x1f449;&#x1f449;&#x1f449;二分类水果数据 1.数据预处理 可以看到有4个特征&#xff0c;2种分类结果&#xff0c;最后4个没有分类结果的数据是拿来预测的 # 1. 数据预处理 import pandas as pd df pd.read_excel(oridata/二分类水果数据.xlsx,use…

开源大模型LLaMA 2会扮演类似Android的角色么?

在AI大模型没有商业模式&#xff1f;等文章中&#xff0c;我多次表达过这样一个观点&#xff1a;不要把大模型的未来应用方式比喻成公有云&#xff0c;大模型最终会是云端操作系统的核心&#xff08;新通用计算平台&#xff09;&#xff0c;而它的落地形式会很像过去的沃森&…

【C++】开源:Linux端ALSA音频处理库

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍Linux端ALSA音频处理库。 无专精则不能成&#xff0c;无涉猎则不能通。。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c…

Python增删改查小练习

目录 1. List操作-增加 2. List操作-查询 3. List操作-修改 4. List操作-删除 资料获取方法 1. List操作-增加 List Append(“xx”) 插入到列表尾部 Insert(x,xx) 在指定的位置插入 Extend 将列表的元素分开,插入到之前列表的尾部 小练习: 把一个字符串”abcdefg…

ssh打开远程vscode

如果想要远程打开其他终端的vscode&#xff0c;首先要知道远程终端的ip地址和用户名称以及用户密码 1、打开本地vscode 2、点击左下角蓝色区域 3、页面上部出现如下图&#xff0c;点击ssh&#xff0c;我这里已经连接&#xff0c;所以是connect to host 4、选择Add New SSH Host…

appium中toast识别

目录 一、什么是Toast&#xff1f; 二、环境前提 三、修改配置 四、安装驱动 五、常见报错及解决方案 1、cnpm 不识别&#xff0c;提示不是内部或外部命令 2、npm 也不识别 3、报错 六、代码节选 一、什么是Toast&#xff1f; Android中的Toast是一种简易的消息提示框…

比selenium体验更好的ui自动化测试工具: cypress介绍

话说 Cypress is a next generation front end testing tool built for the modern web. And Cypress can test anything that runs in a browser.Cypress consists of a free, open source, locally installed Test Runner and a Dashboard Service for recording your tests.…

day44-Spring_AOP

0目录 1.2.3 1.Spring_AOP 实体类&#xff1a; Mapper接口&#xff1a; Service和实现类&#xff1a; 测试1&#xff1a; 运行后&#xff1a; 测试2&#xff1a;无此型号时 测试3&#xff1a;库存不足时 解决方案1&#xff1a;事务声明管理器 测试&#xff1a…

RocketMQ主从集群broker无法启动,日志报错

使用vmWare安装的centOS7.9虚拟机&#xff0c;RocketMQ5.1.3 在rocketMQ的bin目录里使用相对路径的方式启动broker&#xff0c;jps查询显示没有启动&#xff0c;日志报错如下 排查配置文件没有问题&#xff0c;nameServer也已经正常启动 更换绝对路径&#xff0c;启动broker&…

[ELK使用篇]:SpringCloud整合ELK服务

文章目录 一&#xff1a;前置准备-(参考之前博客)&#xff1a;1.1&#xff1a;准备Elasticsearch和Kibana环境&#xff1a;1.1.1&#xff1a;地址&#xff1a;[https://blog.csdn.net/Abraxs/article/details/128517777](https://blog.csdn.net/Abraxs/article/details/1285177…

MySQL explain详解

文章目录 0 环境准备1 explain 之 id2 explain 之 select_type3 explain 之 table4 explain 之 type5 explain 之 key6 explain 之 rows7 explain 之 extra MySQL 的 EXPLAIN 是一个用于查询优化的关键字。它用于分析和评估查询语句的执行计划&#xff0c;帮助开发者理解查询语…

尚硅谷大数据项目《在线教育之采集系统》笔记001

视频地址&#xff1a;尚硅谷大数据项目《在线教育之采集系统》_哔哩哔哩_bilibili 目录 P004 P006 P007 P009 P010 P017 P025 P026 P027 P028 P030 P004 将数据以图形图表的方式展示出来&#xff01; P006 数据埋点 所谓埋点就是在应用中特定的流程收集一些信息&…

(css)自定义登录弹窗页面

(css)自定义登录弹窗页面 效果&#xff1a; 代码&#xff1a; <!-- 登录弹窗 --> <el-dialog:visible.sync"dialogVisible"title"用户登录"width"25%"centerclass"custom-dialog":show-close"false":close-on-cli…

【NLP】温和解读:transformer的核心思想

变压器模型及其关键组件的概述。 一、介绍 在这篇博文中&#xff0c;我将讨论本世纪最具革命性的论文“注意力是你所需要的一切”&#xff08;Vaswani et al.&#xff09;。首先&#xff0c;我将介绍自我注意机制&#xff0c;然后介绍变形金刚的架构细节。在之前的博客文章《从…

基于C#的无边框窗体动画效果的完美解决方案 - 开源研究系列文章

最近在整理和编写基于C#的WinForm应用程序&#xff0c;然后碰到一个其他读者也可能碰到的问题&#xff0c;就是C#的Borderless无边框窗体的动画效果问题。 在Visual Studio 2022里&#xff0c;C#的WinForm程序提供了Borderless无边框窗体的样式效果&#xff0c;但是它没提供在无…