多线程应用——单例模式

单例模式

文章目录

  • 单例模式
    • 一.什么是单例模式
    • 二.如何实现
      • 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.hqwc.cn/news/89979.html

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

相关文章

Vue.js2+Cesium1.103.0 十一、Three.js 炸裂效果

Vue.js2Cesium1.103.0 十一、Three.js 炸裂效果 Demo ThreeModelBoom.vue <template><div:id"id"class"three_container"/> </template><script> /* eslint-disable eqeqeq */ /* eslint-disable no-unused-vars */ /* eslint-d…

使用U盘重装Windows10系统详细步骤及配图【官方纯净版】

文章目录 1.制作启动盘1.1准备U盘及一台电脑1.2下载win10安装包 2.安装操作系统2.1插入系统安装盘2.2设置启动盘为第一启动项2.3开始安装操作系统 3.安装成功后进入图形界面3.1启动问题3.2驱动问题3.3调出"控制面板"3.4给磁盘分区 4.win10激活 前天下午不知道怎么想的…

LeetCode--HOT100题(45)

目录 题目描述&#xff1a;199. 二叉树的右视图&#xff08;中等&#xff09;题目接口解题思路 PS: 题目描述&#xff1a;199. 二叉树的右视图&#xff08;中等&#xff09; 给定一个二叉树的 根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序…

用反射实现自定义Java对象转化为json工具类

传入一个object类型的对象获取该对象的class类getFields方法获取该类的所有属性对属性进行遍历&#xff0c;并且拼接成Json格式的字符串&#xff0c;注意&#xff1a;通过属性名来推断方法名获取Method实例通过invoke方法调用 public static String objectToJsonUtil(Object o…

向函数传递参数(传地址)

过往课程 向函数传递参数&#xff08;传值、传引用、传const引用&#xff09; 传地址 向函数传地址&#xff0c;是指将变量的地址传递给函数。 函数通过声明参数为地址变量来接收一个变量的地址。 示例如下&#xff1a; #include <iostream> using namespace std;v…

pyechart笔记:opts.AxisOpts

定制化图表的轴线&#xff08;x轴和y轴&#xff09;的样式和设置 0 不设置坐标轴 c1(Bar().add_xaxis([力量,智力,敏捷]).add_yaxis(全能骑士,# 系列名称&#xff0c;用于 tooltip 的显示&#xff0c;legend 的图例筛选。[429,321,296],#系列数据).add_yaxis(猴子,[352,236,4…

一键实现 Oracle 数据整库同步至 Apache Doris

在实时数据仓库建设或迁移的过程中&#xff0c;用户必须考虑如何高效便捷将关系数据库数据同步到实时数仓中来&#xff0c;Apache Doris 用户也面临这样的挑战。而对于从 Oracle 到 Doris 的数据同步&#xff0c;通常会用到以下两种常见的同步方式&#xff1a; OGG/XStream/Lo…

408考研-数据结构算法-顺序表

数组 如何创建数组 我们以 Java 中创建数组为例&#xff0c;创建语法如下 dataType[] arrName new dataType[size];dataType: 也就是我们数组中元素的数据类型arrName:即数组名size:即数组所能容纳的元素数量new: Java 语言中的关键词 假设我们要创建一个由 10 个元素的数…

blender 火焰粒子

效果展示 创建火焰模型 新建立方体&#xff08;shift A &#xff09;,添加表面细分修改器&#xff08;ctrl 2 &#xff09;&#xff0c;视图层级调整为 3 &#xff0c;这样布线更密集&#xff1b; 右键将模型转换为网格&#xff0c;tab 进入编辑模式&#xff0c;7 切换到顶…

五、多表查询-4.1子查询和分类

一、概念 SQL语句中嵌套select语句&#xff0c;成为嵌套查询&#xff0c;又称子查询。 子查询外部的语句 可以是 insert / update / delete / select 的任何一个。 二、子查询分类 1、根据子查询结果不同 标量子查询&#xff08;子查询结果为单个值&#xff09;、列子查询&a…

创建python环境——Anaconda

在Windows中安装Anaconda和简单使用 一.Anaconda发行概述 Anaconda是一个可以便捷获取和管理包&#xff0c;同时对环境进行统一管理的发行版本&#xff0c;它包含了conda、 Python在内的超过180个科学包及其依赖项。 1.Anaconda发行版本具有以下特点&#xff1a; (1)包含了…

1.RabbitMQ介绍

一、MQ是什么&#xff1f;为什么使用它 MQ&#xff08;Message Queue&#xff0c;简称MQ&#xff09;被称为消息队列。 是一种用于在应用程序之间传递消息的通信方式。它是一种异步通信模式&#xff0c;允许不同的应用程序、服务或组件之间通过将消息放入队列中来进行通信。这…