多线程应用——单例模式

单例模式

文章目录

  • 单例模式
    • 一.什么是单例模式
    • 二.如何实现
      • 1.口头实现
      • 2.利用语法特性
    • 三.实现方式(饿汉式+懒汉式)
      • 1.饿汉式
      • 2.懒汉式
      • 3.线程安全的单例模式
      • 4.双重检查锁
      • 5.禁止指令重排序

一.什么是单例模式

单例模式(Singleton Pattern)顾名思义,在程序中一个类只有一个对象实例。例如我们在JDBC编程中,我们创建了一个简单类DataSource,只要从DataSource中获取数据库连接即可,不用创建多个DataSource对象。

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。

二.如何实现

1.口头实现

2.利用语法特性

  • 本质上就是利用编程语言自身的特性,强行限制某个类不能创建多个实例
  • static修饰一个变量后,这个变量就从一个普通的成员变量属性变成了类对象的成员变量
  • 在JVM中一个类只要一个类对象,从而保证了static变量的唯一性

三.实现方式(饿汉式+懒汉式)

1.饿汉式

public class SingletonHungry{//类的成员变量private static Singleton instance=new Singleton();//私有化构造方法private SingletonHungry(){ }/*** 对外获取类成员方法* @return*/public static SingletonHungry getInstance(){return instance;}
}

饿汉式:需要急迫的创建这个实例,类在加载的过程中就创建出来了

描述:这种方式比较常见,但容易产生垃圾对象

  • 优点:没有加锁,执行效率高
  • 缺点:类加载时就初始化,浪费内存

2.懒汉式

public class SingletonLazy{//类的成员变量private static Singleton instance=null;//私有化构造方法private Singleton(){ }/*** 对外获取类成员方法* @return*/public static Singleton getInstance(){//判断一个需要返回的对象是否为空if (instance==null){//创建对象instance=new SingletonLazy();}//返回单例对象return instance;}
}

懒汉式:什么时候用什么时候才去创建,不要程序启动的时候创建,从而节省了程序启动时的开销

3.线程安全的单例模式

在多线程中,饿汉式只是获取变量而不是修改变量;而懒汉式是修改共享变量,因此存在线程安全问题。

我们用上面的代码做一测试

public class Demo_SingletonLazy {public static void main(String[] args) {//多个线程获取单例对象for (int i = 0; i < 10; i++) {Thread thread = new Thread(() -> {SingletonLazy instance = SingletonLazy.getInstance();System.out.println(instance);});thread.start();}}
}

image-20230830183534647

我们知道造成线程安全问题的原因有 原子性、内存可见性、有序性

image-20230830185305419

通过上图分析得出问题:不满足原子性,那该如何解决呢,当然是加锁。

public class SingletonLazy{//类的成员变量private static Singleton instance=null;//私有化构造方法private Singleton(){ }/*** 对外获取类成员方法* @return*/public static Singleton getInstance(){synchronized(SingletonLazy.class){//判断一个需要返回的对象是否为空if (instance==null){//创建对象instance=new SingletonLazy();}}//返回单例对象return instance;}
}

image-20230830185941493

加锁之后,我们看到问题也解决了,但此时还有一个非常严重的问题:效率问题

  1. 当变量没有初始化时,第一次创建可能会出现线程问题,因为多个线程可能创建实例
  2. 当实例变量被创建后,new操作将永远不会执行了,因为获取到的实例不为null了
  3. 那么synchronized的锁就没有必要加了,因为实例已经创建好了,之后线程拿到锁之后只是判断一下实例是否为空,不会去new了,如果不为null就什么也不干就把锁释放了,这样一来锁白加了,资源也白白浪费了

synchronizeed看上去是一个关键字,可能会涉及到用户态–>内核态之间的切换,这个成本是比较高的,我们为了保证程序正确执行的基础可以承担这个成本,但是没有必要做无用的消耗

4.双重检查锁

既然在第一次创建完实例后加锁是为了判断实例是否为空,那么不如将判断为空放到加锁之前,避免因为上述原因而造成资源浪费

public class SingletonDCL {//定义一个类的成员变量private static SingletonDCL instance=null;private SingletonDCL(){}public static SingletonDCL getInstance(){//第一层判断是否需要加锁if (instance==null){synchronized (SingletonDCL.class){//第二层加锁判断是否需要创建对象if (instance==null){//创建对象instance=new SingletonDCL();}}}//返回单例对象return instance;}
}

5.禁止指令重排序

上述代码还存在一个严重问题,那就是指令重排序问题

假设一个线程在调用getInstande()方法时,拿到了锁,进入了第二层开始new对象:

new对象本质分为三步:

  1. 申请内存空间
  2. 调用构造方法,初始化实例
  3. 把内存首地址赋给对象的引用

可以看出1和3有逻辑关系,2是在这个内存空间里填充数据

如果这里指令重排序,造成执行顺序为1 3 2 那么这个时候又有一个线程执行到第一层的判断,这里的instance就不为空了,返回一个没有完成初始化的对象。这种情况也是很危险的

为了防止指令重排序,给变量加入关键字volatile

public class SingletonDCL {//定义一个类的成员变量private static volatile SingletonDCL instance=null;//禁止指令重排序,也保证了在对共享变量修改时的内存可见性private SingletonDCL(){}public static SingletonDCL getInstance(){//第一层判断是否需要加锁if (instance==null){synchronized (SingletonDCL.class){//第二层加锁判断是否需要创建对象if (instance==null){//创建对象instance=new SingletonDCL();}}}//返回单例对象return instance;}
}

看完留个三连吧

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

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

相关文章

基于天鹰算法优化的BP神经网络(预测应用) - 附代码

基于天鹰算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于天鹰算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.天鹰优化BP神经网络2.1 BP神经网络参数设置2.2 天鹰算法应用 4.测试结果&#xff1a;5.Matlab代码 摘要…

[Android]JNI的基础知识

目录 1.什么是JNI 2.配置JNI开发环境NDK 3.创建Native C类型的项目 4. 了解CMakeLists.txt 文件 5.了解native-lib.cpp 文件 6.在 Android 的 MainActivity 中调用 native-lib.cpp 中实现的本地方法 1.什么是JNI JNI&#xff08;Java Native Interface&#xff09;是一…

消息中间件 介绍

MQ简介 MQ,Message queue,消息队列&#xff0c;就是指保存消息的一个容器。具体的定义这里就不类似于数据库、缓存等&#xff0c;用来保存数据的。当然&#xff0c;与数据库、缓存等产品比较&#xff0c;也有自己一些特点&#xff0c;具体的特点后文会做详细的介绍。 现在常用…

java八股文面试[JVM]——JVM内存结构2

知识来源&#xff1a; 【2023年面试】JVM内存模型如何分配的_哔哩哔哩_bilibili

Python文本终端GUI框架详解

今天笔者带大家&#xff0c;梳理几个常见的基于文本终端的 UI 框架&#xff0c;一睹为快&#xff01; Curses 首先出场的是 Curses。 Curses 是一个能提供基于文本终端窗口功能的动态库&#xff0c;它可以: 使用整个屏幕 创建和管理一个窗口 使用 8 种不同的彩色 为程序提供…

计算机竞赛 基于Django与深度学习的股票预测系统

文章目录 0 前言1 课题背景2 实现效果3 Django框架4 数据整理5 模型准备和训练6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于Django与深度学习的股票预测系统 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff…

TypeScript配置-- 1. 新手处理TS文件红色波浪线的几种方式

Typescript 规范化了JS的项目开发&#xff0c;但是对一些项目的一些新手来说&#xff0c;确实是不怎么优好&#xff0c;譬如我&#xff1a;将我之前珍藏的封装JS代码&#xff0c;拿进了配置了tsconfig.json的vue3项目&#xff0c;在vscode下&#xff0c;出现了满屏的红色 &…

HTML总结2 [转]

以下转载和参考自&#xff1a;HTML 表单。 1、表格 可以通过 CSS 设置表格的样式&#xff1a; 如下为将上面table.lamp th,td样式中的padding注释掉&#xff0c;开启table.lamp中的padding的效果&#xff1a; 2、列表 3、导航栏 导航栏使用<ul>列表实现&#xff0c;…

iptables教程

iptables netfilter/iptables&#xff08;简称iptables&#xff09;是与2.4.x和2.6.x系列版本Linux内核集成的IP信息包过滤系统。 Iptables Tutorial 1、表和链 1.1、表 iptables会根据不同的数据包处理功能使用不同的规则表。它包括如下五个表&#xff1a;filter、nat和m…

table,设置 数据相同时, 合并列

<el-table :data"tableData" :span-method"objectSpanMethod" border style"width: 100%" show-summary><el-table-column type"index" label"序号" width"100" /><el-table-column prop"dat…

C语言练习题解析:挑战与突破,开启编程新篇章!(2)

&#x1f493;博客主页&#xff1a;江池俊的博客⏩收录专栏&#xff1a;C语言刷题专栏&#x1f449;专栏推荐&#xff1a;✅C语言初阶之路 ✅C语言进阶之路&#x1f4bb;代码仓库&#xff1a;江池俊的代码仓库&#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐ 文…

加密的PDF文件,如何解密?

PDF文件带有打开密码、限制编辑&#xff0c;这两种密码设置了之后如何解密&#xff1f; 不管是打开密码或者是限制编辑&#xff0c;在知道密码的情况下&#xff0c;解密PDF密码&#xff0c;我们只需要在PDF编辑器中打开文件 – 属性 – 安全&#xff0c;将权限状态修改为无保护…

100天精通Golang(基础入门篇)——第19天:深入剖析Go语言中方法(Method)的妙用与实践

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to Golang Language.✨✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1…

【SQL】关系模型与查询和更新数据

一、关系模型 1.1 主键 主键是关系表中记录的唯一标识。主键的选取非常重要&#xff1a;主键不要带有业务含义&#xff0c;而应该使用BIGINT自增或者GUID类型。主键也不应该允许NULL。 可以使用多个列作为联合主键&#xff0c;但联合主键并不常用。 1.2 外键 FOREIGN KEY …

剪绳子c、c++实现

给你一根长度为 n 的绳子&#xff0c;请把绳子剪成整数长的 m 段&#xff08; m 、 n 都是整数&#xff0c; n > 1 并且 m > 1 &#xff0c; m < n &#xff09;&#xff0c;每段绳子的长度记为 k[1],...,k[m] 。请问 k[1]*k[2]*...*k[m] 可能的最大乘积是多少&#x…

函数(个人学习笔记黑马学习)

1、函数定义 #include <iostream> using namespace std;int add(int num1, int num2) {int sum num1 num2;return sum; }int main() {system("pause");return 0; } 2、函数的调用 #include <iostream> using namespace std;int add(int num1, int num2…

【DRONECAN】(三)WSL2 及 ubuntu20.04 CAN 驱动安装

【DRONECAN】&#xff08;三&#xff09;WSL2 及 ubuntu20.04 CAN 驱动安装 前言 这一篇文章主要介绍一下 WSL2 及 ubuntu20.04 CAN 驱动的安装&#xff0c;首先说一下介绍本文的目的。 大家肯定都接触过 ubuntu 系统&#xff0c;但是我们常用的操作系统都是 Windows&#x…

C++------map和set的使用

文章目录 关联式容器键值对树型结构的关联式容器set的介绍map的介绍 关联式容器 什么是关联式容器&#xff1f;它与序列式容器有什么区别&#xff1f; 关联式容器也是用来存储数据的&#xff0c;与序列式容器不同的是&#xff0c;其里面存储的是<key&#xff0c;value>结…

【拾枝杂谈】从游戏开发的角度来谈谈原神4.0更新

君兮_的个人主页 勤时当勉励 岁月不待人 C/C 游戏开发 Hello,米娜桑们&#xff0c;这里是君兮_&#xff0c;结合最近的学习内容和以后自己的目标&#xff0c;今天又开了杂谈这个新坑&#xff0c;分享一下我在学习游戏开发的成长和自己的游戏理解&#xff0c;当然现在还是一枚…

【LeetCode75】第三十九题 二叉树的右视图

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我们一棵二叉树&#xff0c;让我们返回站在二叉树右边从上到下看到的节点。 那实际上就是要我们对二叉树进行层序遍历&#xff0c…