多线程代码案例之单例模式

news2024/9/20 0:58:55

目录

单例模式

饿汉模式 

懒汉模式

问题一 

问题二 

问题三


单例模式

单例模式,是设计模式的一种。在有些特定场景中,有的特定的类,只能创建出一个实例,不应该创建多个实例。单例模式就可以保证这样的需求。例如JDBC中的DataSource就适用于单例模式。常见的实现单例模式的方式有:饿汗模式和懒汉模式。

饿汉模式 

使用 Singleton 类来创建对象。

private static Singleton instance = new Singleton();

用 static 来修饰,因此 instance的属性与实例无关,而是与类相关。由于类对象在一个java进程中,只有唯一的一份,因此类对象内部的 类属性 也是唯一的一份。

同时,这个类对象的创建是在类加载过程创建的,类加载对于运行过程来说是比较靠前的阶段,这就给人一种 “十分急切” 的感觉,因此也就称为饿汉模式。(因此也可以理解为饿汉模式也就是创建对象特别早)

(java代码中的每一个类,都会在编译完成后得到唯一的 .class 文件。当 jvm 运行的时候,就会加载这个 .class 文件读取其中的二进制指令,并且在内存中构造出对应的类对象,形如下述代码中的 Singleton.class)

static保证了这个实例的唯一性:

1.static使 instance 属性是类属性,类属性是对类对象而言的,类对象又是唯一实例的(在类加载阶段被创造出的一个实例)

2.构造方法是设为 private,因此外面的代码中无法new。

(运行一个java程序,就需要让java进程能够找到并读取对应的 .class 文件。就会读取文件内容,并解析,构造成类对象.....这一系列的过程,也称为类加载过程。要执行 java 程序的前提就是得把类加载起来)

在保证了这个实例的唯一性的同时,也保证了这个实例在一个比较早的时机被创建。

实际上类对象本身和 static 没有关系,而是类里面使用 static 修饰的成员,会作为类属性。也就相当于这个属性对应的内存空间在类对象里。

// 饿汉模式的 单例模式 的实现
// 此处保证 Singleton 这个类只能创建出一个实例
class Singleton{
    //此处先把实例创建出来
    private static Singleton instance = new Singleton();

    // 如果需要使用这个唯一实例,统一通过 Singleton.getInstance() 来获取
    public static Singleton getInstance(){
        return instance;
    }

    // 为了避免 Singleton 类不小心被复制多份出来
    // 把构造方法设为 static ,在类外面,就无法通过 new 的方式来创建这个 Singleton 实例了!
    private Singleton(){}
}

public class ThreadDemo19 {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);
    }
}

在饿汉模式中,如果是多线程调用,只涉及到 “读操作” ,因此是没有线程安全问题的。 

懒汉模式

class SingletonLazy{

    private static SingletonLazy instance = null;

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

从上述代码中,可以看出实例的创建并非是类加载的时候创建了,而是等到真正第一次使用的时候,判断条件:如果instance为null,这个时候才去创建实例。也就是说当不需要使用的时候,就不会创建实例。

这也就是所谓的懒汉模式,虽说是“懒”,但从效率上来看,需要使用的时候才创建,这样的效率是更高的。

问题一 

在懒汉模式中,既涉及到 “读操作”,也涉及到 “写操作”,因此是可能存在线程安全问题的。

通过前面的讲解,也可以发现程序实际上是通过多条指令来执行的,所以在懒汉模式中的getInstance方法中,先大致整体分为如下指令:

load:从内存中读取instance的值;

cmp:对 instance的值与 null 进行比较;

若条件满足,则进行new操作;

save:将new的值赋给instance;

以一种线程不安全的例子来讲解: 

 从中我们就可以看出,这里的线程安全问题,本质上就是读,比较和写操作不是原子的,这就导致了线程t2 读到的值可能是线程t1 还没来得及写的。这也就是脏读问题。

解决办法也就是对这三个操作进行加锁。 

class SingletonLazy{

        private static SingletonLazy instance = null;

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

 这时候 t2线程load得到的结果就是 线程t1 修改后的结果了,也就不再是null值了,因此不再创建新对象,而是返回现有对象。

问题二 

此时的代码,在每次 getInstance 操作的时候都会进行加锁,而加锁操作是有一定开销的;

而实际上,这里的加锁操作只需要针对在 new 出对象之前,才是有意义的。一旦 new 完对象了,后续调用 getInstance ,此时 instance 的值一定是非空的,所以加锁操作是没有必要的,所以可以做出如下优化:如果对象还没创建,就加锁;如果对象已经创建过了,就不用加锁;

 class SingletonLazy{

        private static SingletonLazy instance = null;

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

 加锁 if 条件之后,负责判定是否需要加锁,此时就不再是无脑加锁了,也就提高了运行效率。

问题三

此时的代码依旧存在线程安全问题,假设有很多线程,都去进行 getInstance,这个时候,是有可能会被优化的,正如我们之前所讲的情况:只有第一次读才是真正读了内存,后续的读都是读取寄存器/cache,这也就涉及到内存可见性问题了。

除此之外,指令重排序也是会导致线程安全问题的。

instance = new SingletonLazy();

这条语句会有三条指令:

1.申请内存空间;

2.调用构造方法,把这个内存空间初始化成一个合理的对象;

3.把内存空间的地址赋值给 instance 引用;

正常情况下,是按照123的顺序来执行的,但编译器为了提高程序效率,可能就会调整执行顺序,在多线程的环境下,就会可能出现线程安全问题。(单线程环境下没有关系)

例如:假设线程t1 按照 132的顺序来执行,t1线程执行完1 3之后,在执行2 之前,被切出CPU了,这时候线程t2 进行执行,就会发现instance 的引用非空,那么线程t2 就会直接返回instance引用,并且可能会尝试使用 引用的属性。但由于线程t2 此时拿到的是非法的对象,也就是没构造完成的不完整的对象,再去使用的话就会出现线程安全问题。

因此针对这个问题,就需要使用volatile来修饰,volatile 解决两个问题:

1.内存可见性;2.指令重排序;

因此最终优化后的代码为:

//经典面试题,解决多线程安全问题!!!
// 懒汉模式的 单例模式 的实现
    class SingletonLazy{
    private volatile static SingletonLazy instance = null;      //volatile来解决内存可见性,指令重排序

    public static SingletonLazy getInstance(){
        if(instance == null){                           //不再是无脑加锁,而是满足判断是否为空再加锁
            synchronized (SingletonLazy.class) {                //对类对象进行加锁
                if ( instance == null){
                    instance = new SingletonLazy();
                }
            }
        }

        return instance;
    }

    private SingletonLazy(){}
}
public class ThreadDemo20 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s1 == s2);
    }

}

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

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

相关文章

OpenMMLab AI实战营笔记前两次课

文章目录1计算机视觉算法基础与 OpenMMLabCV引入OpenMMLab基础知识:2 计算机视觉之图像分类算法基础传统方法--设计图像特征AlexNet VGG 等神经网络搜索(2016)Vision/Swin Transformer轻量化卷积神经网络注意力机制 Attention Mechanism模型学…

文档存储Elasticsearch系列--3分布式存储和搜索过程

前言:ES 作为分布式文档的存储,它的存储过程是怎样的,它的分布式检索过程又是怎样的; 1 分布式存储过程: 为了说明目的, 我们 假设有一个集群由三个节点组成。 它包含一个叫 blogs 的索引,有两个主分片&a…

linux查看/设置某个进程运行的CPU核

目录 1.ps -eF 2.top命令 3.pidstat命令 4.使用taskset指令 5.使用taskset指定进程运行在CPU核 1.ps -eF #查看fwd进程运行在哪个cpu核上 [rootCENTOS57 rpm]# ps -eF | grep fwd 2.top命令 (1)top (2)按f键可以选择下面配置选项 P Last Used Cpu (SMP) (3)Es…

【深度学习】YOLO系列(v1-v3+tinyv3)解析

YOLOv1 正负样本选取 如果目标的中心落在cell中,那么这个cell就负责预测这个类别。 由于每个cell预测两个bbox,那么选择与GT IOU大的bbox来预测这个目标,也就是这一个框的 1 i j o b j = 1 , 1 i j n o b j

通过Python的pptx库操作ppt-替换文本和图片-批量生成任意自定义图片

通过Python的pptx库操作ppt-替换文本和图片-批量生成任意自定义图片 作者:虚坏叔叔 博客:https://xuhss.com 早餐店不会开到晚上,想吃的人早就来了!😄 一、前言 这是一个全部的脚本,我们知道,…

阿里巴巴最全Java、架构师、大数据、算法PPT技术栈图册

我只截图不说话,PPT大全,氛围研发篇、算法篇、大数据、Java后端架构!除了大家熟悉的交易、支付场景外,支撑起阿里双十一交易1682亿元的“超级工程”其实包括以下但不限于客服、搜索、推荐、广告、库存、物流、云计算等。 Java核心…

第二章 Linux系统安装

第一节 安装计划 基本思路是使用VMWare这样的虚拟机软件创建一个“虚拟计算机”,在虚拟机上安装Linux系统。 安装vm软件通过vm软件来创建一个虚拟机空间通过vm软件来在创建好的虚拟空间上,安装我们的Centos操作系统使用Centos 第二节 vmware下载安装 和…

python-实现保留3位有效数字(四舍六入五成双规则)

项目场景: 实现保留3位有效数字(四舍六入五成双规则) 问题描述 输入:输出: 1234 123412 12.04 4.000.2 0.2000.32 0.3201.3 1.301.235 1.241.245 1.241.2451 1.25示例分析: 解决代码: from de…

jvm启动流程以及自定义加载器

类加载运行过程,当我们用java命令运行某个类的main函数启动程序时,首先需要通过类加载器把主类加载到JVM。public class Math {public static final int initData 666;public static final User user new User();public int compute() {// 一个方法对应…

【C++】对象与类

【C】对象与类 文章目录【C】对象与类1、定义1.1 对象的定义1.2 类的定义2、对象与类的创建2.1 类的创建2.2 对象的创建3、封装3.1 访问限定符3.2 对封装的解释4、类的实例化5、类、对象大小6、this指针6.1 this指针概念6.2 this指针特点1、定义 1.1 对象的定义 现实世界对对…

写哪个IB科目的EE最易得A?

综合了IB论坛上学生的建议,根据IB毕业生们的看法: E同学:选择你感兴趣的科目写EE。 R同学:我推荐写IB英语EE,在我看来,英语很容易找到你感兴趣的内容,因为英语 EE 适用于诸多的不同主题。我觉得…

录制PPT课件哪个录屏软件好?这3款值得收藏

使用PPT课件进行教学,不仅可以让抽象的知识具体化,还可以让课堂变得更加生动有趣。在制作视频课程时,需要对PPT课件进行录制,那你知道录制PPT课件哪个录屏软件好吗?今天小编就给大家介绍3款值得收藏的录屏软件&#xf…

为什么GIF文件是图像文件而不是视频文件?(GIF文件格式详解)

今天整理硬盘的时候想到一个问题:GIF 是放在静态图像文件里还是视频文件里? 因为放视频里吧,GIF 的分类是静态图像,而且由图像组成;放图像里吧,它又会动。 然后我就开始想:GIF 会动但为什么被归…

分享113个图片切换JS特效,总有一款适合您

分享113个图片切换JS特效,总有一款适合您 113个图片切换JS特效下载链接:https://pan.baidu.com/s/1NNSP-DMf3n0PeNbdNd8jEg?pwdsfwr 提取码:sfwr Python采集代码下载链接:https://wwgn.lanzoul.com/iKGwb0kye3wj jQuery中间…

Java基础学习笔记(十九)—— 多线程

多线程1 多线程相关概念2 多线程的实现方式2.1 继承Thread类2.2 实现Runnable接口2.3 实现Callable接口3 线程休眠4 线程优先级5 守护线程6 线程同步6.1 案例引入6.2 同步代码块6.3 同步方法6.4 Lock锁6.5 死锁1 多线程相关概念 并行与并发: 并行:在同…

未授权和敏感文件泄露

目前存在未授权访问漏洞的服务主要 包括:NFS、Samba、LDAP、Rsync、FTP、GitLab、Jenkins、 MongoDB、Redis、ZooKeeper、ElasticSearch、Memcache、CouchDB、 Docker、Solr、Hadoop等。 redis未授权 通过手工进行未授权访问验证,在安装Redis服务的Kal…

MATLAB 线性整数规划

✅作者简介:人工智能专业本科在读,喜欢计算机与编程,写博客记录自己的学习历程。 🍎个人主页:小嗷犬的个人主页 🍊个人网站:小嗷犬的技术小站 🥭个人信条:为天地立心&…

oracle官网下载指定版本的jdk

文章目录前言下载JDK环境变量前言 oracle官网下载jdk,如果是想下载指定版本的,总是提示需要你登录。如何解决呢? 下载JDK oracle官网 https://www.oracle.com 或者直达此页 https://www.oracle.com/java/technologies/downloads/archive…

springboot相关

2023.2.3 springboot的示例sagan涉及到很多软件技术,运行这个示例在Windows 7下遇到问题。将Windows 7重装为Windows 10专业版。下载了node-v18.14.0-x64.msi,安装node时可以选择安装相关的软件,例如python 3.11.0。下载python 3.11.0太慢了…

07、微服务组件Seata

1、事务简介 事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。在关系数据库中,一个事务由一组SQL语句组成。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。 原子性(ato…