单例模式:懒汉饿汉线程安全问题

news2024/10/6 18:32:00

        在我们前几篇文章中都了解了一些关于线程的知识,那么在多线程的情况下如何创建单例模式,其中的线程安全问题如何解决?


目录

1.什么是单例模式? (饿汉模式)

2.单例模式(懒汉模式)

*懒汉模式与懒汉模式的对比

*如何解决懒汉模式下线程不安全问题? 


1.什么是单例模式? (饿汉模式)

        单例模式:某个类,在进程中只有唯一的实例,不能new多次。例如如下代码:

class Singleton{
    private static Singleton singleton = new Singleton();

    public static Singleton getSingleton() {
        return singleton;
    }
    //将构造方法设置为private禁止外部重新创建
    private Singleton(){
        //空着就行
    }
}
public class ThreadDemo12 {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getSingleton();
        Singleton s2 = Singleton.getSingleton();
//        外部不允许再new这个类的实例
//        Singleton s3 = new Singleton();
    }
}

        我们通过操作,来禁止外部在产生新的实例
        1.将成员变量Singleton设置成static.标志着次成员变量是类的静态成员.那自然只有一份.
        2.将类的构造方法设置成private.那外部就不能再创建其他的实例本体了.
        3.提供getter方法,只有调用getter方法才能获取唯一实例本体.

        我们可以发现s1,s2这两个实例是同一个,不信的玉粉可以打印一句:sout(s1==s2);看是否是true.但是s3就不可以了.这种单例模式我们称为"饿汉模式".意味着我们在新建成员变量的时候,就new实例,只是在调用getter方法的时候返回这个实例本体.下面我再介绍一种单例模式:"懒汉模式".

2.单例模式(懒汉模式)

        观察懒汉模式的例子: 

class SingletonLazy{
    private static SingletonLazy singleton = null;
    private SingletonLazy(){};//构造方法

    public static SingletonLazy getSingleton() {
        if(singleton == null){
            singleton = new SingletonLazy();
        }
        return singleton;
    }
}
public class ThreadDemo13 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getSingleton();
        SingletonLazy s2 = SingletonLazy.getSingleton();
        System.out.println(s1==s2);
        //不能执行新的new操作
//        SingletonLazy s3 = new SingletonLazy();
        
    }

        懒汉模式就意味着:我们在创建成员变量的时候,先不进行new操作,先让其=null;然后再调用getSingleton()方法的时候,去判断它是否为null,如果为null,代表着当前成员未被创建,就new;否则代表当前对象已经存在就返回这个对象即可.

*懒汉模式与懒汉模式的对比

观察上述两个案例代码思考一个问题:
在多线程模式在哪个模式有线程安全问题?懒or饿or都有or都没有?   why?

 

正确答案是:  饿汉模式没有线程安全问题,懒汉模式有线程安全问题!!! 

那么为什么会这样呢? 我们要注意,此处有个大前提:多线程下!!!在多线程环境下,我们饿汉模式突出的是"急迫""急需",而懒汉模式顾名思义就是"非必要不创建""从容".试想在线程快速调度的情况中,饿汉模式是有优势的:无论怎么调度,我上来就new,

private static Singleton singleton = new Singleton();

这一行代码是原子性的,无法拆分,这样就能保证我的对象只能被new一次.在对应的getSingleton()方法中也是"只读"操作,我们说过"只读"情况下是没有线程安全问题的而懒汉模式则是先不new,需要了再new,但是你new的时候有经过一系列的判断,新建new然后返回,万一在途中被切走了,那你new的对象就不止一个了.

*如何解决懒汉模式下线程不安全问题? 

那么我们需要寻找到一中解决办法:保证懒汉模式在多线程环境下线程安全问题:有以下三步:

  1. 为了保证原子性,需要加锁.
  2. 为了防止线程多次调度下创建多个对象,需要双重 if 判定.
  3. 为了禁止指令重排序,需要加上volatile 关键字. 

下面我们分别来解析其中的道理:

1.加锁

         我们可以直观的对比出来:懒汉模式与饿汉模式最大的区别就是:饿汉模式的new操作是原子性的.那么我们已经熟悉过可以打包代码的方法-----加锁,那么第一步就是给getSingleton()方法加锁,这里你既可以给方法前缀加上关键字synchronized,也可以在if 判断的时候加上synchronized,但是万万不可以这样加锁:

这样相当于没加,因为你要确保你的原子性是if 判定所包含的所有内容,所以稳妥的办法是在if 外面加锁:like that:

 

千万注意别把锁加错位置了!!! 还是不明白的玉粉可能你需要仔细研究一下"懒汉模式与饿汉模式的区别"......

2.双重if 判定. 

那么我们为什么需要双重if 判定呢?双重 if 怎么写呢?来看正确案例:

 

        有些玉粉可能就疑惑了:俩if 判定一模一样啊????为什么???  这里我要强调一下:不是if 长的一样就代表一个意思,也不是代码赘余了,这两个if 有不同的初心!!!在多线程的环境下,第一个if判断的是"是否要加锁",因为加锁操作实际上是非常低效的操作,加锁就可能有阻塞,如果没有第一个if判定,那么我们只要调用getSingleton()方法就会触发"锁竞争",是非常不友好的.第二个if判断的是,线程无论是否经历了调度,加锁后的singleton是否还是null.因为在两个if判定中间有加锁操作,加锁意味着有可能出现"锁竞争",有可能会发生"阻塞",等到真加上锁了,其中线程可能已经被切换了N次,那么这时候就有种"士别三日""如隔春秋"的感觉了,这时候的singleton是不是还未被其他线程创建就不得而知了,那就必须再次判定,如果"此singleton"还是"彼singleton"那就继续new吧,如果不是就直接返回singleton对象了.....

3.volatile关键字 

        这里是小玉一直不太懂的地方,现在终于懂了也希望和大家分享一下心得:在讲加锁操作关键字synchronized的时候,我们说synchronized能禁止指令重排序这个说法存疑!!!不然我们发明什么volatile干什么?volatile才是明确的1.用来保证内存可见性2.用来禁止指令重排序,但是在多线程创建对象的时候不存在什么"内存可见性"这一说,所以它再次的作用只是用来禁止指令重排序的.
        试想一下:在你创建对象new操作的时候,大致分为三步:1.申请内存. 2.调用构造方法初始化. 3.返回对象地址. 指令重排序可能会让new操作从正常的123变成132.如果执行顺序真的是132,那么1完成之后该3了,此时线程被调度走了,其他线程可能会以为该对象是完整的对象,那么在访问它的属性的时候,就会发现它其实是一个没有初始化的"空壳子",里面没有方法没有属性...什么都干不了......所以禁止指令重排序是必要操作,那么更改完的代码如下:

class SingletonLazy{
    volatile private static SingletonLazy singleton = null;
    private SingletonLazy(){};//构造方法

    public static SingletonLazy getSingleton() {
        if(singleton == null) {
            synchronized (SingletonLazy.class) {
                if (singleton == null) {
                    singleton = new SingletonLazy();

                }
            }
        }
        return singleton;
    }
}
public class ThreadDemo13 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getSingleton();
        SingletonLazy s2 = SingletonLazy.getSingleton();
        System.out.println(s1==s2);
        //不能执行新的new操作
//        SingletonLazy s3 = new SingletonLazy();

    }

}

如此就没有线程安全问题了...............


        好了小玉先讲这么多,其实小玉在这一篇想讲一下"阻塞队列""生产者消费者模型"的,因为看了b站 的一个视频印象很深刻,感觉很有东西可以讲,所以就文思泉涌想开始写,但是没有单例模式的铺垫很难讲好这些东西,所以就换成了将单例模式及懒汉&饿汉了.whatever,小玉下一章就可以将这些内容了,过年了小玉有些偷懒,最近心情也不是很好,有一个繁琐的事对心境造成了影响,写博客可以说是我的排解途径之一吧......期待小玉吧! 小玉会继续努力的!!!!!!!
        在此祝大家新年快乐,龙年小玉在实现自己的梦想,希望大家&玉粉也能梦想成真!!!

 

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

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

相关文章

VTK 正交投影 透视投影

1.VTK默认透视投影(近大远小); 1.调用vtkCamera的ParallelProjectionOn函数开启 2.通过vtkCamera的SetParallelScale缩放 3.通过vtkCamera的SetClippingRange设置前后裁剪平面 2.正交投影(平行投影,远近一样&#xf…

Netty Review - NioEventLoopGroup源码解析

文章目录 概述类继承关系源码分析小结 概述 EventLoopGroup bossGroup new NioEventLoopGroup(1); EventLoopGroup workerGroup new NioEventLoopGroup();这段代码是在使用Netty框架时常见的用法,用于创建两个不同的EventLoopGroup实例,一个用于处理连…

Linux第51步_移植ST公司的linux内核第3步_添加修改设备树

1、设备树文件的路径 1)、创建linux中的设备树头文件 在“my_linux/linux-5.4.31/arch/arm/boot/dts/”目录中,以“stm32mp15xx-edx.dtsi”为蓝本,复制一份,并命名为 “stm32mp157d-atk.dtsi”,这就是我们开发板的设备树头文件。…

【DDD】学习笔记-四色建模法

或许正是认识到彩色 UML 在建模过程的不足之处,ThoughtWorks 的徐昊才在彩色 UML 基础之上提出了自己的“四色建模法”。可考的四色建模法资料仅见于徐昊在 InfoQ 上发表的文章运用四色建模法进行领域分析。在这篇文章中,徐昊回答了建模活动的一个关键问…

【开源】基于JAVA+Vue+SpringBoot的房屋出售出租系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 房屋销售模块2.2 房屋出租模块2.3 预定意向模块2.4 交易订单模块 三、系统展示四、核心代码4.1 查询房屋求租单4.2 查询卖家的房屋求购单4.3 出租意向预定4.4 出租单支付4.5 查询买家房屋销售交易单 五、免责说明 一、摘…

Kafka 之生产者(Producer)

目录 一. 前言 二. 生产消息 三. 幂等和事务 四. send() 发送消息 五. 原理解析 一. 前言 Kafka生产者是一个应用程序,它负责向 Kafka 主题发送消息。这些消息可以用于多种目的,如记录用户活动、收集物联网设备数据、保存日志消息或缓存即将写入数据…

《乱弹篇(十四)香火旺》

连日来,“大年初一烧香祈福,北京雍和宫人山人海”这一词条登上社交网站热搜,对这一现象的描述多为“初一凌晨 民众在雍和宫前排大队”,“大年初一,雍和宫内人山人海,烟雾缭绕”,“雍和宫迎来6万…

Asp .Net Core 系列:Asp .Net Core 集成 NLog

简介 NLog是一个基于.NET平台编写的日志记录类库,它可以在应用程序中添加跟踪调试代码,以便在开发、测试和生产环境中对程序进行监控和故障排除。NLog具有简单、灵活和易于配置的特点,支持在任何一种.NET语言中输出带有上下文的调试诊断信息…

三、案例 - MySQL数据迁移至ClickHouse

MySQL数据迁移至ClickHouse 一、生成测试数据表和数据1.在MySQL创建数据表和数据2.在ClickHouse创建数据表 二、生成模板文件1.模板文件内容2.模板文件参数详解2.1 全局设置2.2 数据读取(Reader)2.3 数据写入(Writer)2.4 性能设置…

ctfshow-文件上传(web151-web161)

目录 web151 web152 web153 web154 web155 web156 web157 web158 web159 web160 web161 web151 提示前台验证不可靠 那限制条件估计就是在前端设置的 上传php小马后 弹出了窗口说不支持的格式 查看源码 这一条很关键 这种不懂直接ai搜 意思就是限制了上传类型 允许…

计算机组成原理 1 概论

主要内容 介绍运算器、控制器、存储器结构、工作原理、设计方法及互连构成整机的技术。 主要内容: ◼ 数值表示与运算方法 ◼ 运算器的功能、组成和基本运行原理 ◼ 存储器及层次存储系统 ◼ 指令系统 ◼ CPU功能、组成和运行原理 ◼ 流水线 ◼ 系统总线 ◼ 输入输出…

UE5 播放本地MP3、MP4

1.创建一个媒体播放器 2.如创建视频,勾选。 它会多一个媒体纹理给你 3.1 设置音频 在一个actor上添加“媒体音频组件” “音频媒体播放器”赋值给它 3.2播放音频 添加一个音频媒体播放器变量, 赋值 地址使用绝对地址 4.1设置视频 UI上创建一个imag…

快速学习Spring

Spring 简介 Spring 是一个开源的轻量级、非侵入式的 JavaEE 框架&#xff0c;它为企业级 Java 应用提供了全面的基础设施支持。Spring 的设计目标是简化企业应用的开发&#xff0c;并解决 Java 开发中常见的复杂性和低效率问题。 Spring常用依赖 <dependencies><!-…

Linux:信号的处理

文章目录 信号处理 本篇总结的是关于信号的处理 信号处理 在之前有这样的观点&#xff1a;信号在合适的时候被处理好&#xff0c;当进程收到信号后&#xff0c;当前进程可能在做优先级更高的事&#xff0c;所以它来不及处理这个信号&#xff0c;那么就会把这个信号暂时保存起…

spring aop @annotation的用法

直接看原文: spring aop annotation的用法-CSDN博客 -------------------------------------------------------------------------------------------------------------------------------- annotation用在定义连接点时&#xff0c;对连接点进行限制。比如我们想对标注了…

双非本科准备秋招(18.2)—— 图解Monitor

对象头 普通对象&#xff1a; 数组对象&#xff1a; java中对象存储结构分为对象头&#xff08;Header&#xff09;、实例数据&#xff08;Instance Date&#xff09;和对齐填充&#xff08;Padding&#xff09;。 对象头存储着Mark Word和Klass Word&#xff0c;通过Klass Wo…

【MySQL】操作库 —— 库的操作 -- 详解

一、增删数据库 1、创建数据库 create database db_name; 本质就是在 /var/lib/mysql 创建一个目录。 说明&#xff1a; 大写的表示关键字。[ ] 是可选项。CHARACTER SET&#xff1a;指定数据库采用的字符集。COLLATE&#xff1a;指定数据库字符集的校验规则。 2、数据库删除…

背包问题(理论)

对于面试的话&#xff0c;掌握01背包、完全背包&#xff0c;就够用了&#xff0c;最多可以再来一个多重背包。 至于背包九讲其他背包&#xff0c;面试几乎不会问&#xff0c;都是竞赛级别的了&#xff0c;leetcode上连多重背包的题目都没有&#xff0c;所以题库也告诉我们&…

NodeJS安装(windows)

NodeJS安装&#xff08;windows&#xff09; 1、官网地址 NodeJS官网地址&#xff1a;https://nodejs.org/en 2、安装 3、验证NodeJS环境变量 cmd后&#xff0c;运行&#xff1a;node -v 4、配置npm的全局安装路径&#xff08;需要管理员身份运行&#xff09; npm conf…

【后端高频面试题--设计模式上篇】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;后端高频面试题 &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; 什么是设计模式&#xff1f;怎么理解设计模式&#xff1f; 设计模式是在软件设计中&#xff0c…