单例模式及其线程安全问题

news2025/4/7 17:40:41

目录

1.设计模式

2.饿汉模式

3.懒汉模式

4.线程安全与单例模式


1.设计模式

设计模式是什么?

设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案

这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的

单例模式的作用就是保证某个类在程序中只存在唯一一份实例,不会创建出多个实例(之前学过的JDBC编程,DataSource这样的类就适合单例模式)

特点:

  • 1、单例类只能有一个实例
  • 2、单例类必须自己创建自己的唯一实例
  • 3、单例类必须给所有其他对象提供这一实例

单例模式分为"饿汉""懒汉"两种

2.饿汉模式

//饿汉模式
//此处保证只能创建一个实例
class Singleton{
    private static Singleton instance = new Singleton();
    //想要使用时,通过Singleton.getInstance()来获取!
    public static Singleton getInstance(){
        return instance;
    }
    //构造方法私有化,类外无法通过new来调用构造器创建实例!!
    private Singleton(){}
}
public class Thread {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1==singleton2);
    }
}

构造器私有化之后是不能通过new来调用构造器实例化对象的

此处我们将这个实例设置成私有的,通过get方法来获取,并且将构造方法私有化,不能创建新实例,因此访问这个实例的时候,每次访问得到的是同一个引用 

 

private static Singleton instance = new Singleton();

Singleton这个属性和实例无关,是和类相关的,java代码中的每个类在编译完成后都会得到.class文件,JVM运行时会加载这个文件读取其中的二进制指令,并在内存中构造对应的类对象(Singleton.class),这个过程就是类加载的过程

该模式是如何保证实例唯一呢

1.static修饰的实例instance,让当前实例的属性是类属性.在类加载阶段就被创建,一个类只加载一次,这个实例只创建唯一一份

2.构造方法私有化,类外无法再创建新的实例 

这个单例模式的名称是"饿汉模式",这个名字的来由是与后面的"懒汉模式"相比较得出的,体现在:在类加载阶段,就直接创建出了实例,实在很靠前的阶段给人一种急迫的感觉,所以叫饿汉模式

3.懒汉模式

//懒汉模式
class SingletonLazy{
    private static SingletonLazy instance = null;
    public static SingletonLazy getInstance() {
        if(instance == null){
            instance = new SingletonLazy();
        }
        return instance;
    }
    private SingletonLazy(){};
}
public class Thread {
    public static void main(String[] args) {
        SingletonLazy singleton3 = SingletonLazy.getInstance();
        SingletonLazy singleton4 = SingletonLazy.getInstance();
        System.out.println(singleton3==singleton4);
    }
}

 

懒汉模式的实例初始情况下是null,并非是在类加载时就创建出来了,而是第一次使用的时候才创建出来的,如果没有使用,那么就不创建了,单从效率来说是更好的选择

4.线程安全与单例模式

上述两种模式在多线程环境下调用getInstance是否是线程安全的呢?

先分析一下饿汉模式

 在饿汉模式中.多线程调用只涉及到了"读" 操作,我们知道多个线程只读一个变量是安全的,那么这个饿汉模式就是安全的

再看懒汉模式

 这里涉及到了"读和写"两个操作,在多线程中调用,是不安全的

上述途中两个线程调用时,由于随机调度和指令重排序的特点,如果在t1线程还没有创建出实例,t2线程就调用,那么instance还是null,继续往下执行,那么t1t2会创建出两个实例,触发多次new操作了,就不满足单例模式这个应用场景的需求了!!导致了线程不安全

如何解决这个线程安全问题呢?

刚才的安全问题根本原因是读,比较,写这三个操作不是原子的,导致了t2读到的值可能是t1没来得及写的(脏读)

加锁肯定是解决线程安全问题的普适方法

public static SingletonLazy getInstance() {
        if(instance == null){
            synchronized (SingletonLazy.class){
                instance = new SingletonLazy();
            }
        }
        return instance;
    }

这种加锁方式是不可取的!!这里只给new操作加锁了,那么t2还是可能读到t1没来得及写的数据,所以我们要给整个操作加锁! 保证读,比较,new,写这几个操作整体是原子的,正确的加锁方法如下

public static SingletonLazy getInstance() {
    synchronized (SingletonLazy.class){
        if (instance == null) {
            instance = new SingletonLazy();
        }
    }
    return instance;
}

到这里t2读到的就是t1更新过的数据了,是一个非空值,不会触发if条件,也就不能new新的实例了,满足了单例模式的要求

但是我们每个线程调用get时都要加锁,加锁操作也是有开销的,频繁的加锁会降低效率.我们发现一旦有一个实例后,后续调用get时,instance肯定是非空的,就直接触发return,那么就不需要锁了!

所以我们再进行一个判定,如果对象还没创建就加锁,创建过了,就不加锁!

这种方式采用双校验锁机制,安全且在多线程情况下能保持高性能

getInstance() 的性能对应用程序很关键时使用

public static SingletonLazy getInstance() {
        if(instance == null){
            synchronized (SingletonLazy.class){
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

1处的if语句是判定是否需要加锁!

2处的if语句是判定是否要创建实例对象!

这两个连续相同的if语句在没有加锁的情况下是没有意义的,一个两个效果相同,但是中间加了锁,就可能引起线程阻塞,等到解锁之后,第一个if和第二个if之间对于计算机来说已经沧海桑田了!程序内部的状态,变量的值都可能发生很大改变

这样减少了不必要的加锁,但是还存在内存可见性问题!

假设有很多线程都来调用get,这个时候第一次调用是读内存,后续都是读寄存器/cache,那么就会有被优化的风险!

还有指令重排序引入的线程安全问题,new操作可以拆分为三个步骤

1.申请内存空间

2.调用构造方法,初始化对象

3.把空间地址赋给instance引用

编译器可能会为了提高程序效率将指令执行顺序调整,1不会被调整.23会被调整,单线程情况写123,132没有本质区别,最后都能new出实例对象,但是多线程情况下,t1如果执行132,执行到13后就被切换到t2来执行,此时t1的2还没有执行,instance仍然是一个null,t2却认为t1已经执行完3了,那么此处的引用就是非null的了,按照代码t2会直接返回一个instance引用,可能还会尝试使用引用中的属性,但是这是一个非法的实例对象,它并没有被构造完成!

解决内存可见性,指令重排序问题需要用到关键字--volatile

所以要使用volatile修饰instance!!这样就能解决内存可见性和指令重排序

 线程安全的饿汉版单例模式:

class SingletonLazy{
    private volatile static SingletonLazy instance = null;
    public static SingletonLazy getInstance() {
        if(instance == null){
            synchronized (SingletonLazy.class){
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy(){};
}

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

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

相关文章

各类指针的详细介绍

🏖️作者:malloc不出对象 ⛺专栏:《初识C语言》 👦个人简介:一名双非本科院校大二在读的科班编程菜鸟,努力编程只为赶上各位大佬的步伐🙈🙈 目录前言一、基本数据类型指针1.1 指针的…

漏洞丨cve2017-11882

作者:黑蛋 一、漏洞简介 本次漏洞还是一个office溢出漏洞,漏洞编号cve-2017-11882。该漏洞是office一个组件EQNEDT32.EXE引起的栈溢出,通杀office版本2007-2016。 二、复现环境 系统版本 目标程序 调试工具 辅助工具 win7 sp1 x86 off…

【MySQL基础教程】DDL语句详细介绍

前言 本文为 【MySQL基础教程】DDL语句 相关相关内容进行详尽介绍,下边将对数据库操作(包括:查询所有数据库、查询当前数据库、创建数据库、删除数据库、切换数据库等),表操作(包括:查询创建、数…

微信小程序分包及案例

文章目录5. 分包6. 独立分包7. 分包预下载8. 案例-自定义tabbar5. 分包 分包指的是把一个完整的小程序项目,按照需求划分为不同的子包,在构建时打包成不同的分包,用户在使用 时按需进行加载。 可以优化小程序首次启动的下载时间在多团队共同…

微信公众号开发—扫描二维码实现登录方案

😊 作者: 一恍过去💖 主页: https://blog.csdn.net/zhuocailing3390🎊 社区: Java技术栈交流🎉 主题: 微信公众号开发—扫描二维码实现登录方案⏱️ 创作时间: 2022…

非零基础自学Golang 第13章 并发与通道 13.1 概述

非零基础自学Golang 文章目录非零基础自学Golang第13章 并发与通道13.1 概述13.1.1 并行与并发13.1.2 Go并发优势第13章 并发与通道 并发是指在同一段时间内,程序可以执行多个任务。 随着社会需求的发展,光靠硬件的提升是无法满足高并发的需求的&#…

[前端攻坚]:数组去重的几种方法

总结一些日常需要用到的一些api,也是在一些面试中会经常出现的题目,今天分享的是数组去重的几个不同的方法, 同时文章也被收录到我的《JS基础》专栏中,欢迎大家点击收藏加关注。 数组去重的方法 1.set去重 2.map去重 3.for循环in…

Python安装Pycrypto

前言 安装 使用以下命令安装 pip install pycrypto2.6.1报错 如果在安装过程中出现如下错误 则说明系统缺乏相应python开发包,需要进行安装对应的python开发包 解决 在CentOS下,如果是python2.7则使用如下命令安装 yum install python-devel是pyt…

Pytest用例运行及规范

温馨提示 本篇约1600字,看完需3-5分钟,学习学半小时,加油! 先看普通函数运行顺序 import pytestdef test_one():print("我是清安")def test_02():print("--02--")def test_a():print("--a--")de…

BP神经网络的最简Python实现

文章目录神经元BP原理及实现测试BP,就是后向传播(back propagation),说明BP网络要向后传递一个什么东西,这个东西就是误差。 而神经网络,就是由神经元组成的网络,所以在考虑BP之前,还不得不弄清楚神经元是…

endata 电影票房响应数据破解

本文仅供参考学习,如有侵权可联系本人 目标网站 aHR0cHM6Ly93d3cuZW5kYXRhLmNvbS5jbi9Cb3hPZmZpY2UvQk8vWWVhci9pbmRleC5odG1s加密入口分析 在异步请求那里可以看到请求接口,请求参数并未加密只是响应内容进行了加密,暂时也无法判断加密方…

JavaWeb的Servlet学习之Request03

目录 1.Request 1.1Request执行流程 1.2request对象和response对象的原理 1.3 request对象继承体系结构 1.4request功能: 1.3.1获取请求消息数据 1.获取请求行数据 2.获取请求头 3.获取请求体数据 4.其他功能 4.1获取请求参数通用方式:不论get…

开源CA搭建-基于openssl实现数字证书的生成与分发

目录 一、前言 二、openssl介绍 三、openssl的常用用法 (一)单向加密 (二)生成随机数 (三)生成公钥,私钥 1.生成私钥 2.提取公钥 四、搭建CA (一)创建根CA私钥…

Linux的camera驱动 摄像头调试方法

CameraInfo类用来描述相机信息,通过Camera类中getCameraInfo(int cameraId, CameraInfo cameraInfo)方法获得, 主要包括以下两个成员变量facing,facing 代表相机的方向, 它的值只能是CAMERA_FACING_BACK(后置摄像头&am…

Golang 【basic_leaming】1 基本语法

阅读目录Go 语言变量Go 语言 - 声明变量1. 标准格式2. 批量格式Go 语言 - 初始化与定义变量1. 标准格式2. 编译器推导类型格式3. 短变量声明与初始化Go语言 - 多变量同时赋值Go 语言 - 匿名变量参考资料Go 语言整型(整数类型)1 自动匹配平台的 int 和 un…

新项目为什么决定用 JDK 17了

大家好,我是风筝。公众号「古时的风筝」,专注于后端技术,尤其是 Java 及周边生态。文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面。 最近在调研 JDK 17,并且试着将之前…

阴差阳错,阴阳之变

北京的第一批“杨康”们已经返回到工作岗位,这其中就包括我。简单总结一下我的感染和康复过程,给大家做个样本吧。我属于北京放开的第一波感染者,12.9日当天感觉嗓子干,毫不犹豫,果然是中招了;周末开始发烧…

特朗普发行NFT惹群嘲,上线售罄现“真香定律”

文/章鱼哥出品/陀螺财经特朗普14日在其创建的社交平台truth social上发帖称,“美国需要一个超级英雄”。他还预告自己将于当地时间15日宣布“重大消息”。据《新闻周刊》报道,特朗普当日在其社交平台上发了一段十几秒的视频,里面有一个他站在…

Windows实时运动控制软核(三):LOCAL高速接口测试之C++

今天,正运动小助手给大家分享一下MotionRT7的安装和使用,以及使用C对MotionRT7开发的前期准备。 01 MotionRT7简介 MotionRT7是深圳市正运动技术推出的跨平台运动控制实时内核,也是国内首家完全自主自研,自主可控的Windows运动控…

Linux搭建测试环境详细步骤

本文讲解如何在Linux CentOS下部署Java Web项目的步骤 环境准备 (1)Linux系统(2)JDK(3)Tomcat (4)MySQL工具下载 一、Linux系统 本文主要是Linux CentOS7为例 自己在家练习小项…