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

2023-11-14

目录

单例模式

饿汉模式

懒汉模式 


前言

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


单例模式

顾名思义,就是一个程序只能含有一个实例,有的场景中,希望一个类只能有一个对象,例如 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;
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

多线程案例(1) - 单例模式 的相关文章

随机推荐

  • 如何使用Blender建3D汉字(保姆级别的详细)

    未来的游戏开发程序媛 现在的努力学习菜鸡 本专栏是我关于建模的学习笔记 本篇是如何使用Blender建3D汉字 这是这个专栏的第一篇 因为不知道把这篇放到哪里 就开了个新的 如何使用Blender建3D汉字 就这种的 如果是的话可以接着往下
  • 计算机科学和PYTHON编程导论_15_概率与分布

    随机程序 掷骰子 import random def rollDie 返回一个1 6的随机整数 return random choice 1 2 3 4 5 6 def rollN n result for i in range n res
  • Matlab回归分析

    线性回归 在实际中 对于情况较复杂的实际问题 因素不易化简 作用机理不详 可直接使用数据组建模 寻找简单的因果变量之间的数量关系 从而对未知的情形作预报 这样组建的模型为拟合模型 拟合模型的组建主要是处理好观测数据的误差 使用数学表达式从数
  • 用gdb.attach()在gdb下断点但没停下的情况及解决办法

    在python中 如果导入了pwntools 就可以使用里面的gdb attach io 的命令来下断点 但是这一次鼠鼠遇到了一个情况就是下了断点 但是仍然无法在断点处开始运行 奇奇怪怪 这是我的攻击脚本 我们运行一下 可以看到其实已经运行
  • Python入门--with语句

    with语句 上下文管理器 with语句可以自动管理上下文资源 不论什么原因跳出with块 都能确保文件的正确关闭 以此来达到释放资源的目的 with open 上下文管理器 with open a txt r as file as起个别名
  • Unity3d-游戏中的小地图制作

    方法一 利用NGJ MiniMap插件 1 导入该插件后 在Mesh Version gt Prefabs中 将NJG MiniMap 2D 拖入到场景中 2 在Hierarchy中点击MiniMap 位于NJG MiniMap 2D gt
  • inc si指令的作用_到底什么是链接,它起到了什么作用?

    几十年以前 计算机刚刚诞生 人们编写程序时 将所有的代码都写在同一个源文件中 经过长期的积累 程序包含了数百万行的代码 以至于人们无法维护这个程序了 于是人们开始寻找新的方法 迫切地希望将程序源代码分散到多个文件中 一个文件一个模块 以便更
  • 如何 debug (调试) maven 插件?

    用惯了 IntelliJ IDEA debug 功能 你知道如何在 IntelliJ IDEA 上调试 maven 插件吗 哈哈哈 那就是这篇文章准没错了 1 准备源码 准备你想要进行 debug 的 maven 插件的源代码 我这里以 m
  • ARM常用汇编指令

    目录 一 汇编基本语法 1 汇编指令的最典型书写模式 二 常用汇编指令 1 push压栈指令 2 pop出栈指令 3 sub指令 4 add指令 5 movs数据传输指令 6 str指令 7 ldr指令 8 bl指令 9 MOVW指令 10
  • C++不定参数,模板函数,模板类详解附实例

    前言 在 C 中 有时我们在写一个函数时并不知道参数的数量和类型 这时需要用到不定参数 模板函数 正文 不定参数 不定参数怎么表示 对于不定参数的表示 就是三个点 注意是英文的点 那么我们在正常使用时函数参数写成这样 funtionType
  • Python 容器序列切片

    视频版教程 Python3零基础7天入门实战视频教程 序列是指内容连续且有序的一类数据容器 前面学的列表 元组 字符串都是序列 并且支持下标索引 切片是指从一个序列中 取出一个子序列 语法 序列 起始下标 结束下标 步长 返回一个新的序列
  • 短文阅读3:Variational Autoencoders (VAEs)

    深度生成网络 VAEs introduction 降维方法 PCA and Autoencoders 降维架构 PCA 问题1 什么是自动编码器autoencoder PCA和Autoencoders之间的关系 Variational Au
  • 【建议收藏】数据库 SQL 入门——数据查询操作(内附演示)

    引言 在上一节中 我们讨论了DML的使用方法 本节我们继续开始DQL的学习 首先回归一下DQL的基于定义 DQL Data Query Language 数据查询语言 用来查询数据库中表的记录 在本节中我们主要讨论DQL的用法以及基本语法
  • 计算机视觉之人脸识别(Yale数据集)--HOG和ResNet两种方法实现

    1 问题描述 在给定Yale数据集上完成以下工作 在给定的人脸库中 通过算法完成人脸识别 算法需要做到能判断出测试的人脸是否属于给定的数据集 如果属于 需要判断出测试的人脸属于数据集中的哪一位 否则 需要声明测试的人脸不属于数据集 这是一个
  • 思维导图 函数

  • PCL点云处理之最小二乘空间直线拟合(3D) (二百零二)

    PCL点云处理之最小二乘空间直线拟合 3D 二百零二 一 算法简介 二 实现代码 三 效果展示 一 算法简介 对于空间中的这样一组点 大致呈直线分布 散乱分布在直线左右 我们可采用最小二乘方法拟合直线 更进一步地 可以通过点到直线的投影 最
  • 5款程序员必备的免费在线画图工具,超级好用!

    点击上方 芋道源码 选择 设为星标 管她前浪 还是后浪 能浪的浪 才是好浪 每天 10 33 更新文章 每天掉亿点点头发 源码精品专栏 原创 Java 2021 超神之路 很肝 中文详细注释的开源项目 RPC 框架 Dubbo 源码解析 网
  • java中的集合基础

    集合介绍 集合类的特点 提供一种存储空间可变的存储模型 存储的数据容量可以发生改变 集合和数组的区别 共同点 都是存储数据的容器 不同点 数组的容量是固定的 集合的容量是可变的 数组可以存基本数据类型和引用数据类型 集合只能存引用数据类型
  • 【Android进阶篇】WebView显示网页详解

    概述 WebView是Android用于显示网页的控件 通过WebView 我们可以查看本地的网页 也可以查看网络资源 本文内容如下 一 加载本地网页 二 加载网络资源 三 在WebView中使用JavaScript和CSS 四 WebCh
  • 多线程案例(1) - 单例模式

    目录 单例模式 饿汉模式 懒汉模式 前言 多线程中有许多非常经典的设计模式 这就类似于围棋的棋谱 这是用来解决我们在开发中遇到很多 经典场景 简单来说 设计模式就是一份模板 可以套用 单例模式 顾名思义 就是一个程序只能含有一个实例 有的场