多线程案例(1) - 单例模式

news2025/1/19 14:28:18

目录

单例模式

饿汉模式

懒汉模式 


前言

多线程中有许多非常经典的设计模式(这就类似于围棋的棋谱),这是用来解决我们在开发中遇到很多 "经典场景",简单来说,设计模式就是一份模板,可以套用。


单例模式

顾名思义,就是一个程序只能含有一个实例,有的场景中,希望一个类只能有一个对象,例如 JDBC 中的 DataSourse 实例就只需要一个。虽说程序员可以在编写代码时只给类创建一个对象,但是人毕竟没有机器靠谱,所以大佬们就设计了一套模板,按照模板来写代码,就不会出大的差错。

实现单例模式的方法有两种:饿汉模式 和 懒汉模式。

饿汉模式

就是在类加载时,就创建实例。

class SingleDemo{

    //在类加载的时候创建
    private static SingleDemo instance = new SingleDemo();

    //设置为private是为了防止在其他类中 new 一个实例,这样就不是单例模式了
    private SingleDemo(){ }

    //既然不能在外面创建实例,我们就要提供一个方法来得到这个唯一的实例
    public static SingleDemo getInstance() {
        return instance;//读取操作
    }
}

懒汉模式 

顾名思义,就是在用到实例的时候再创建实例,与饿汉模式相比,提高了代码的效率。

class SingleLazyDemo{

    private static SingleLazyDemo instance = null;

    //设置为private是为了防止在其他类中new一个实例
    private SingleLazyDemo(){ }

    public static SingleLazyDemo getSingleLazyDemo(){
        //第一次需要实例时,创建实例
        if(instance == null){
            //修改操作
            instance = new SingleLazyDemo();
        }
        return instance;//读取操作
    }
}

了解了什么是饿汉模式和懒汉模式,我有一个问题:上述两种写法,那种是线程安全的?

我在前几篇博客中提到过,如果多个线程,同时修改同一个变量,此时就可能出现线程安全问题,所以显而易见,饿汉模式是线程安全的,它的方法中只涉及到读取操作,而懒汉模式是线程不安全的,它的方法中涉及到读取和修改操作。画个图理解一下:

 接下来,我们就来解决懒汉模式的线程安全问题。导致该模式出现线程安全的原因其实在图中已经体现出来了,这个一个非原子操作,针对这一问题,我们的解决方法就是加锁。

class SingleLazyDemo{
    private static SingleLazyDemo instance = null;

    //设置为private是为了防止在其他类中new一个实例
    private SingleLazyDemo(){ }

    public static SingleLazyDemo getSingleLazyDemo(){

        synchronized (SingleLazyDemo.class){//使修改操作变成原子操作

            if(instance == null){//第一次需要实例时,创建实例
                instance = new SingleLazyDemo();
            }

        }
        return instance;
    }
}

此时,虽然懒汉模式的线程安全问题基本得到了解决,但是一旦这么写,后续每次调用 getInstance,都需要先加锁,而加锁的开销是很大的,只要涉及加锁,那么该代码就基本与"高性能"无缘了。实际上,我们的实例化对象的操作(即修改操作) 只是出现在第一次调用 getInstance 的时候。

一旦对象被new出来了,后续的线程调用 getInstance 就没有必要加锁了,因为这时候只用读取操作,线程是安全的,所以我们还需要再添加一个条件:

class SingleLazyDemo{
    private static SingleLazyDemo instance = null;

    //设置为private是为了防止在其他类中new一个实例
    private SingleLazyDemo(){ }

    public static SingleLazyDemo getSingleLazyDemo(){
        if(instance == null){//判断是否线程安全,要不要加锁
            synchronized (SingleLazyDemo.class){//使修改操作变成原子操作
                if(instance == null){//判断是否实例化
                    instance = new SingleLazyDemo();
                }
            }
        }
        return instance;
    }
}

注意:这两个 if 虽然都是判断 instance 是否为 null, 但是第一个 if 实际上是借此判断线程是否要加锁,如果为null,就说明需要执行修改操作,线程不安全,要加锁,如果不为null,说明线程只要执行读取操作,线程安全,不要加锁。而第二个 if 则是借此判断是否要实例化对象。

在经过上述修改后,此代码还有一个问题,这就涉及到了之前没细讲的指令重排序问题,该问题也是因为编译器优化导致的,编译器为了提高执行效率,可能会在逻辑顺序不变的情况下,调整原有代码的执行顺序。

比如:new 操作,可以分成三步:1. 申请内存空间  2. 在内存空间上构造对象  3. 把内存的地址赋值给 instance 引用。在单线程中,new 操作可以按照 1 2 3 执行,也可以按照 1 3 2 执行,但是在多线程中,1 3 2 这样执行就可能导致线程安全问题。

举个例子:当 t1 线程执行完 1 3 时,instance 就已经是非空了,这个时候 2 还没有执行,t2 线程就开始执行,因为这个时候 instance 非空,所以 t2 线程直接返回 instance,这个时候如果 t2 线程中的代码访问 Instance 中的属性和方法,那么就会出现BUG,因为 Instance 还没有构造对象。

这个问题就需要使用 volatile 关键字来修饰 Instance,这样就可以保证 Instance 在 new 的过程中不会出现指令重排序的现象,下面是最终的代码:

class SingleLazyDemo{
    private static volatile SingleLazyDemo instance = null;

    //设置为private是为了防止在其他类中new一个实例
    private SingleLazyDemo(){ }

    public static SingleLazyDemo getSingleLazyDemo(){

        if(instance == null){//判断是否加锁

            synchronized (SingleLazyDemo.class){//使修改操作变成原子操作

                if(instance == null){//判断是否实例化
                    instance = new SingleLazyDemo();
                }
            }
        }
        return instance;
    }
}

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

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

相关文章

江西服装学院图书馆藏《乡村振兴战略下传统村落文化旅游设计》许少辉八一新著

江西服装学院图书馆藏《乡村振兴战略下传统村落文化旅游设计》许少辉八一新著

浅谈C++|STL之string篇

一.string的基本概念 本质 string是C风格的字符串,而string本质是一个字符串 string和char * 区别 char * 是一个指针string是一个类,类内部封装了char *,管理这个字符串,是一个char * 型容器。 特点 string类内部封装了很多成…

Unity技术手册-UGUI零基础详细教程-Canvas详解

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 👉关于作者 专注于Android/Unity和各种游…

1275MPaMJ螺纹螺栓 最新标准 学习记录

声明 本文是学习GB-T 42850-2023 1275MPaMJ螺纹螺栓. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 10 min~15 min。 A.1.5.8 水冲洗 表面清理后迅速将零件放在21℃~93℃自来水中冲洗,自来水应符合GB5749 的要求。 A.1.6 表…

【qiankun乾坤】从0到1搭建微前端

微前端是一种将一个大型单体应用拆分成多个小型应用的架构方式。它可以让不同的团队独立开发部署自己的应用,同时这些应用可以集成到一个统一的底座应用中,对用户来说就是一个完整的应用。 qiankun 是阿里开源的一个微前端实现框架,可以帮助我们比较容易地实现微前端架构。 下…

Linux中执行bash脚本报错/bin/bash^M: bad interpreter: No such file or directory

文章目录 参考博客: Linux中执行bash脚本报错/bin/bash^M: bad interpreter: No such file or directory 首先在此对这位博主表示感谢。 运行bash脚本会出现两个文件,1037.err和1037.out。 1037.err的文件内容如下: /data/home/user12/.lsbat…

5.11.Webrtc接口的设计原理

在上节课中呢,我向你介绍了web rtc的接口宏,那有很多同学会产生疑问啊,那觉得web rtc为什么要把接口设计的这么复杂?还非要通过宏来实现一个代理类,再通过代理类来调用到web rtc内部。 那为什么要这么设计呢&#xf…

Vulnhub系列靶机---HarryPotter-Fawkes-哈利波特系列靶机-3

文章目录 信息收集主机发现端口扫描dirsearch扫描gobuster扫描 漏洞利用缓冲区溢出edb-debugger工具msf-pattern工具 docker容器内提权tcpdump流量分析容器外- sudo漏洞提权 靶机文档:HarryPotter: Fawkes 下载地址:Download (Mirror) 难易程度&#xff…

SpringBoot/SpringCloud开源项目,快速开发提高效率80%

前言 SpringBoot 是一个非常流行的 Java 框架,它可以帮助开发者快速构建应用程序。他不仅继承了 Spring 框架原有的优秀特性,而且还通过简化配置来进一步简化了 Spring 应用的整个搭建和开发过程。下面我将来介绍若干个项目,带你来快速开发。…

ChatGPT OpenAI 完成Excel组合函数Vlookup+match多条件查找

ChatGPT OpenAI 现在已经助力职场办公。 我们现在有这样一个Excel需求: 根据姓名与科目查找对应的分数。可以使用Vlookup+match组合函数一起来实现 。 我们将公式复制到Excel中来进行验证。 ChatGPT生成的Excel函数公式可以直接进行使用。 更多实战内容。

找不到msvcp140_codevet_ids.dll,无法继续执行代码的解决方法分享

在使用 Photoshop(简称 PS)时,很多用户都遇到过找不到 msvcp140_codevet_ids.dll 的问题。这使得他们无法正常运行 PS,给工作和生活带来了诸多不便。本文将详细介绍 msvcp140_codevet_ids.dll 的相关知识,以及如何解决…

2023-09-13 LeetCode每日一题(检查骑士巡视方案)

2023-03-29每日一题 一、题目编号 2596. 检查骑士巡视方案二、题目链接 点击跳转到题目位置 三、题目描述 骑士在一张 n x n 的棋盘上巡视。在 有效 的巡视方案中,骑士会从棋盘的 左上角 出发,并且访问棋盘上的每个格子 恰好一次 。 给你一个 n x …

TypeScript类型系统层级

🎬 岸边的风:个人主页 🔥 个人专栏 :《 VUE 》 《 javaScript 》 ⛺️ 生活的理想,就是为了理想的生活 ! 目录 1. 顶层类型(Top Type) 1.1 any 类型 1.2 unknown 类型 2. 底层类型(Bottom …

linux设备树节点添加新的复位属性之后设备驱动加载异常问题分析

linux设备树节点添加新的复位属性之后设备驱动加载异常问题分析 1 linux原始设备驱动信息1.1 设备树节点信息1.2 linux设备驱动1.3 makefile1.4 Kconfig1.5 对应的defconfig文件 2 修改之后的linux设备驱动2.1 修改之后的设备树节点信息2.2 原始test_fw.c出现的问题以及原因分析…

学习Bootstrap 5的第十四天

目录 Toast 如何创建 Toast 实例 打开 Toast 实例 滚动监听(Scrollspy) 如何创建滚动监听 实例 侧边栏导航(Offcanvas) 如何创建 Offcanvas 侧边栏 实例 侧边栏的方向 实例 设置背景及背景是否可滚动 实例 侧边栏案例 实例 Toast Toast 组件类似警告框&#…

抽奖界面及功能

1.代码 <template><view class"container"><view class"container_circle" v-for"(v,i) in circleList" :key"i":style"{top:v.topCirclerpx,left:v.leftCirclerpx,background-color:(i%2 0)?oneCircleColor:t…

大厂面试题:有了 G1 还需要其他垃圾回收器吗?

Java全能学习面试指南&#xff1a;https://javaxiaobear.cn 今天我们主要来看下这两个高频的面试考题&#xff1a; G1 的回收原理是什么&#xff1f;为什么 G1 比传统 GC 回收性能好&#xff1f; 为什么 G1 如此完美仍然会有 ZGC&#xff1f; 我们在上一篇中&#xff0c;简要…

Flask+pyecharts结合,html统计图呈现在前端页面

是我,是你 在网上看到这样一段话: “很多时候,你必须接受这世界上突如其来的失去。洒了的牛奶,遗失的钱包,走散的爱人,断掉的友情。当你做什么都于事无补的时候,唯一能做的,就是让自己努力好过一点。” <

iptables 目标地址转换

目录 一、实验准备 二、配置web服务器 三、配置web防火墙网卡 四、配置客户机网卡 五、测试 1、开启防火墙功能&#xff0c;设置源地址转换&#xff0c;通过改变我客户机的地址身份为web服务器同网段来实现访问 2、通过改变目标地址&#xff08;客户机&#xff09;的地址…

无法加载文件xxx,此系统上禁止运行脚本

在 VsCode 跑一个前端项目的时候出现如下错误&#xff1a;此系统上禁止运行脚本 解决&#xff1a;在终端执行指令设置 脚本执行策略 为 RemoteSigned 以管理员身份运行vs code&#xff1b;查看当前执行策略&#xff1a;get-ExecutionPolicy&#xff0c;如显示 Restricted 则表…