【JavaEE初阶】深入解析单例模式中的饿汉模式,懒汉模式的实现以及线程安全问题

news2024/11/19 10:36:44

前言:

🌈上期博客:【JavaEE初阶】深入理解wait和notify以及线程饿死的解决-CSDN博客

🔥感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客

⭐️小编会在后端开发的学习中不断更新~~~  

🥳非常感谢你的支持

  

目录

📚️1.引言

📚️2.两种单例模式

2.1单例模式设计意义

2.2饿汉模式

1.饿汉模式介绍

2.饿汉模式实现

3.饿汉模式测试

2.3懒汉模式

1.懒汉模式介绍

2.懒汉模式实现

3.懒汉模式测试

2.4两种单例模式的区别

📚️3.线程安全问题

3.1两种模式线程安全

3.2懒汉模式线程安全分析和解决

1.随机调度问题

 解决办法

2.重复上锁问题

解决办法

3.指令重排序问题

解决办法

📚️4.总结


📚️1.引言

 OK啊!!!小伙伴们,在上期继小编讲解过wait和notify的使用的问题后,本期将开始实现关于开发中常用的单例模式,那么废话不多说,直接步入正题,go go go~~~;

且听小编讲解,包你学会!!! 

📚️2.两种单例模式

2.1单例模式设计意义

 单例模式(单个实例对象)即一种设计模式,是为我们程序猿需要掌握的一门技能,遵守设计模式,可以保住我们写代码的技术下限;

最主要的原因:在某个进程中只能创建一个实例,所以使用到单例模式可以帮助实现对我们的所写代码的检查和校验

为啥要实现单例模式:例如我们需要使用一个对象对大量数据进行管理,假如这些数据有10GB,所以一旦不小心多创建一个实例对象,那么消耗的内存空间就会成倍进行增长;所以我们需要机器检查人为创造多个实例时就会报错,实现只存在一个实例对象~~~

2.2饿汉模式

1.饿汉模式介绍

所谓的饿汉,即十分迫切的意思,这里在单例模式设计中,表示实例一旦被加载了,这个实例就已经创建了,即实例的创建非常早,相当于程序一启动,那么实例就已经创建了~~~

2.饿汉模式实现

代码实现如下:

class Singleton{
    private static Singleton instance=new Singleton();//即类对象这里只有唯一的静态类
    //实例化后,如果其他线程要进行调用
    public static Singleton getInstance(){
        return instance;
    }
    //将构造方法定位私有,就无法进行new操作
    private Singleton(){}
}

代码解释:

private static Singleton instance=new Singleton();//即类对象这里只有唯一的静态类

这里就是创建了一个名字为instance的静态对象变量,并初始化为一个Singleton的类的实例,每个类的类对象只有一个,类对象中的静态属性也只有一个,所以instance所指的对象也只有一个~~~

public static Singleton getInstance(){
        return instance;
    }

这里即创建了一个静态方法,由于实例对象的访问范围是一个私有的,所以要借助这个方法实现在其他类使用这个私有对象;

private Singleton(){}

这里的私有构造方法是为了实现单例模式所使用的一种写法,可以导致其他类中在使用这个对象时,就无法进行new操作,无法创建一个新的实例;

3.饿汉模式测试

1.当我们实现一个new一个新的实例的时候,我们可以发现此时代码直接报错了;

如图所示:

 解释:很明显可以看到此时报错为:这个Singleton为私有属性,不能创建一个新的实例,这就实现了单例模式;

2.当我们通过“==”符号进行比较地址的时候,可以发现此时为“true”;

代码实例:

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

那么很明显就是小编这里的instance静态对象访问权限为共有的,所以这里可以通过类名直接调用,一般情况下饿汉模式这里的实例对象为私有的哈~~~

2.3懒汉模式

1.懒汉模式介绍

这里和上述的饿汉模式,有异曲同工之妙,这里的懒是指创建的实例的时间在啥时候使用,那么就啥时候创建实例~~~

2.懒汉模式实现

代码实现如下:

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

代码解释:

private static Lazyton instance=null;

这里即创建了一个静态对象,但是这里并没有进行实例化,是为了下方调用方法时来进行必要的实例化对象;

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

这里就是懒汉模式的精髓了,通过调用方法来实现对象的实例化,在进行实例化之前我们要判断这个对象是否已经被实例化过了,如果没有就进行实例化,如果有那么就直接返回这个对象;

private Lazyton(){}

最后这里实现构造方法的私有化,实现在其他类中不能可直接通过new关键词来实例化新的对象;

3.懒汉模式测试

 这里的测试和上述的饿汉模式基本一致,只不过是创建实例的时间不同而已~~~

如图所示:

并且获取的两个对象是一样的,那么测试输出就为“true”,小编这里就不再过多演示了~~~

2.4两种单例模式的区别

这两种单例模式的区别最主要就是两者创建实例的时间不同,这会带来什么好处呢?

注意:

1.第一种情况

在某些程序中,是不需要创建的实例的,而饿汉模式会导致,不管这个程序使用或者不使用,都会创建这个实例对象;然而在不需要实例的情况下使用懒汉模式会比使用饿汉模式省下创建实例这个操作,提升效率~~~;

 2.第二种情况

在某些情况下,假如有一个文件有10GB,使用饿汉模式,编辑器就会将10GB的数据加载到内存当中,然后统计展示;而使用懒汉模式,编辑器会展示一小部分数据,随着用户翻页,再次加载一部分,而不是全部加载出来;这会导致饿汉模式的情况下效率低,而且不符合实际~~~

📚️3.线程安全问题

3.1两种模式线程安全

在上述我们举例说明了关于两种模式的区别和实现,我们可以发现饿汉模式感觉是不如懒汉模式的那么真的是这样吗???答案是否定的;

注意:由于饿汉模式在创建实例的时候,是直接返回实例对象的如下代码:

public static Singleton getInstance(){
        return instance;
    }

 那么此时就可以发现饿汉模式只有“读”操作,我们知道只有读操作是线程安全

注意:此时我们可以发现对比之下,懒汉模式中:

 if (instance==null){
      instance=new Lazyton();
 }  

那么此时可以发现懒汉模式既有“读”操作,又有“写”操作,那么此时我们就知道此时线程是不安全的,存在问题,那么问题是什么呢???

3.2懒汉模式线程安全分析和解决

1.随机调度问题

由于线程的随机调度的特性,如果存在两个线程随机调度,就有以下的情况:

解释:此时我们就可以看到,若第一个线程在执行到if语句的时候,满足条件,突然被调度走了,那么此时线程2,插入直接实例化了一个对象,然后线程1又被调度回来,执行剩下的语句,又创建了一个实例,那么此时就创建了两个实例,这就不是单例模式了~~~

 解决办法

此时我们就可以使用前面讲解的synchronized来实现这段代码打包成原子性的代码,这里即可解决

代码实现如下:

public static Lazyton getInstance(){//由于线程安全问题,加锁
            synchronized (lock){
                if (instance==null){
                    instance=new Lazyton();
                }
            }
        return instance;
    }

此时若加锁后,即使被调度走了,由于锁的竞争,会导致线程2进入阻塞状态,保证线程1能够执行完;所以就解决了这个随机调度的问题~~~

2.重复上锁问题

为啥会重复进行上锁呢???我们一起看看代码:

            synchronized (lock){
                if (instance==null){
                    instance=new Lazyton();
                }
            }

解释:首先我们可以发现不管谁调用这个方法,都会使用synchronized,来进行上锁,我们希望的是在进行实例化过后,这个锁就不要再添加了,即调用这个方法直接返回这个对象实例,如过一直进行加锁,会导致代码执行的效率不高,与“高性能”就无关了~~~

解决办法

这里我们就可以添加一个判定是否需要添加锁的条件,即一个if语句,这里就可以解决问题了

代码如下:

 public static Lazyton getInstance(){//由于线程安全问题,加锁
        if (instance==null){
            synchronized (lock){
                if (instance==null){
                    instance=new Lazyton();
                }
            }
        }
        return instance;
    }

那么此时我们在进行加锁的时候,判定这个实例是否已经被创建了,若创建了就不用进入加锁,进行下面的代码片段了,直接返回实例,反之就加锁,进行实例化对象,这就提高了执行效率~~~

3.指令重排序问题

这里是由指令重排序引起的线程安全问题,和内存可见性问题一样,也是编译器优化程序的一种方式,为啥重新排列会提高效率呢???

举个例子:

此时我们买东西的效率就大大提升了,同理这里的指令执行也是一样的~~~

instance=new Lazyton();

这里的代码可以拆成三个步骤:

1.申请一段内存空间

2.在这个内存上调用构造方法,创建实例

3.将这个内存地址赋值给instance引用变量

那么此时一般来说执行步骤为123,但是编译器可能会优化成132~~~,那么就造成了一下的问题

如图所示:

此时由于随机调度的问题,此时就会导致如上图所示的情况;

在申请内存空间后,并将地址赋值给instance引用变量时,突然被另一线程抢占执行后,发现此时不满足instance==null那么就会直接跳出if条件语句;

注意:这里如果此时线程二还有其他的代码运用这个实例的操作的属性和方法,可能就会导致一个严重的问题,因为这些属性都是未初始化的为“0”的值,那么这里就可能导致代码的逻辑问题;

解决办法

这里就要使用I一个关键词,即volatile,volatile的作用:

1.保证内存可见性问题,每次访问都要读取内存中的数据,而不会优化到寄存器中;

2.禁止指令重排序,对于volatile修饰的变量的读或者写操作的相关指令,是不能重写排序的

那么解决以上问题后,代码如下:

 private static Object lock=new Object();
    private static volatile Lazyton instance=null;
    public static Lazyton getInstance(){//由于线程安全问题,加锁
        if (instance==null){
            synchronized (lock){
                if (instance==null){
                    instance=new Lazyton();
                }
            }
        }
        return instance;
    }

 那么此时就是通过volatile修饰instance引用变量后,保证进行实例化时不会导致指令重排序而造成的线程安全问题了~~~

📚️4.总结

💬💬小编本期讲解了关于设计模式之单例模式中的两个重要模式,即饿汉模式和懒汉模式,关于他们的实现代码,小编也进行了编写;以及最重要的两个模式的线程安全问题,小编进行了注重分析问题的产生,以及如何解决都有涉及~~~

🌅🌅🌅~~~~最后希望与诸君共勉,共同进步!!!


💪💪💪以上就是本期内容了, 感兴趣的话,就关注小编吧。

                             😊😊  期待你的关注~~~

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

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

相关文章

YOLOv8改进 - 注意力篇 - 引入LSKA注意力机制

一、本文介绍 作为入门性篇章,这里介绍了LSKA注意力在YOLOv8中的使用。包含LSKA原理分析,LSKA的代码、LSKA的使用方法、以及添加以后的yaml文件及运行记录。 二、LSKA原理分析 LSKA官方论文地址:LSKA文章 LSKA注意力机制(大可分…

胤娲科技:揭秘AI记忆宫殿—LLM如何用动画玩转乔丹打篮球的秘密

当AI遇上“乔丹打篮球”,真相竟然藏在动画里? 想象一下,你向一位AI大模型轻声询问:“迈克尔・乔丹从事的体育运动是……”几乎在瞬间,它就自信满满地回答:“篮球!” 这一刻,你是否曾…

ROS理论与实践学习笔记——2 ROS通信机制之服务通信

服务通信也是ROS中一种极其常用的通信模式,服务通信是基于请求响应模式的,是一种应答机制。也即: 一个节点A向另一个节点B发送请求,B接收处理请求并产生响应结果返回给A,用于偶然的、对时时性有要求、有一定逻辑处理需求的数据传输…

电脑usb接口控制软件有哪些?六款软件帮你轻松管控USB端口!

小明(疑惑地):“小李,我们公司最近对数据安全特别重视,我听说可以通过软件来控制电脑的USB接口,防止数据泄露。你知道有哪些好用的USB接口控制软件吗?” 小李(自信地)&a…

双十一买什么好?五大双十一好物推荐!

每年的双十一购物节都是消费者期待已久的盛事,届时各大电商平台纷纷推出优惠活动,吸引了无数购物爱好者的目光。双十一买什么好?为了帮助大家在双十一期间高效购物,我们精心挑选了五大双十一好物推荐!这些产品不仅在品…

C++之STL—函数对象谓词

函数对象(仿函数) 函数对象(仿函数)是一个**类**,不是一个函数 类名() 仿函数 直接调用: 、 谓词 定义:返回类型为bool 类型的仿函数 一元谓词:operator()接受一个参数 二元谓词&a…

智能家居新体验:Zigbee2MQTT与Tuya生态的完美结合

01 前言 本文章原文发表于我的微信公众号,请大家关注阅读,涉及的源代码等都在公众号,请搜索公众号: 智能家居NodeRed和HomeAssistant 即可关注。 02 概述 在智能家居领域,Zigbee2MQTT已经成为了许多爱好者和开发者的…

常见字符函数和字符串函数(下)

1. strncpy 函数的使用 将源的前 number 个字符复制到目标。如果在复制 num 个字符之前找到源 C 字符串的末尾(由 null 字符表示),则目标将填充零,直到写入总数 num 个字符为止。如果 source 长于 num,则不会在 destin…

目标检测 DETR(2020)

文章目录 前言backbone位置编码(二维)encoder、decoderprediction heads损失函数计算 前言 DETR全称是Detection Transformer,是首个基于Transformer的端到端目标检测网络,最大的特点就是不需要预定义的先验anchor,也…

项目没亮点?那就来学下pk功能设计吧

先赞后看,南哥助你Java进阶一大半 麻省理工学院开源的Redis adapter适配器,可以将事件广播到多个单独的 socket.io 服务器节点。这一点和下文精彩的内容相关。 我是南哥,一个Java学习与进阶的领路人。 相信对你通关面试、拿下Offer进入心心念…

湖州市自闭症寄宿学校:个性化教育培养孩子潜能

在湖州市,自闭症寄宿学校正积极探索个性化教育的道路,致力于为自闭症儿童提供最适合他们成长与发展的教育环境。这一理念不仅在当地得到了实践,更在全国范围内产生了深远的影响。今天,我们将目光投向广州,深入了解星贝…

头戴式蓝牙耳机哪个品牌比较好?西圣、声阔、QCY热款实测性能PK

头戴式蓝牙耳机凭借其卓越的音质表现、沉浸式的听音体验以及出色的降噪功能,成为了众多音乐爱好者和通勤人士的首选,随着技术的不断进步,西圣、声阔、QCY等知名品牌纷纷推出了各具魅力的头戴式蓝牙耳机产品,面对它们家的耳机&…

十进制与ip地址转换公式(EXCEL公式)

1、十进制转为ip地址公式 TEXT(INT(C2/16777216),“0”)&“.”&TEXT(INT((C2-INT(C2/16777216)*16777216)/65536),“0”)&“.”&TEXT(INT((C2-INT(C2/16777216)*16777216-INT((C2-INT(C2/16777216)*16777216)/65536)*65536)/256),“0”)&“.”&TEXT(MO…

城市空间设计对居民生活质量的影响:构建宜居城市的蓝图

在快节奏的现代生活中,城市不仅是经济活动的中心,更是人们生活、工作、休闲的综合载体。本文旨在深入探讨城市空间设计如何通过科学规划、人性化考量以及生态融合,为居民打造更加宜居、和谐的生活环境。 1. 促进社区互动与归属感 城市空间设…

揭秘FlashAttention:提升注意力计算的速度与内存效率

论文题目:FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness 论文地址:https://arxiv.org/pdf/2205.14135 今天分享一篇论文《FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness》,…

The First项目报告:解读跨链互操作性平台Wormhole

在加密领域,随着公链种类越来越丰富,彼此之间的相对独立,犹如一座座孤悬海外的孤岛,不利于自身生态的发展,因此从资产跨链开始,越来越多的跨链技术被研发出来,多链成为当前区块链的主流概念&…

2024.9.26C++作业

1. 什么是虚函数,什么是纯虚函数? 1.虚函数在基类中声明,使用virtual关键字修饰成员函数,并且允许在派生类中重写。 2.在运行时,允许基类指针或者引用调用这个函数时,根据实际对象类型调用派生类&#xff…

安卓主板_MTK4G/5G音视频记录仪整机及方案定制

音视频记录仪方案,采用联发科MT6877平台八核2* A78 6* A55主频高达2.4GHz, 具有高能低耗特性,搭载Android 12.0智能操作系统,可选4GB32GB/6GB128GB内存,运行流畅。主板集成NFC、双摄像头、防抖以及多种无线数据连接,支…

YOLOv8改进,YOLOv8改进损失函数采用Powerful-IoU(2024年最新IOU),助力涨点

摘要 边界框回归(BBR)是目标检测中的核心任务之一,BBR损失函数显著影响其性能。然而,观察到现有基于IoU的损失函数存在不合理的惩罚因子,导致回归过程中锚框扩展,并显著减缓收敛速度。为了解决这个问题,深入分析了锚框扩展的原因。针对这个问题,提出了一种新的Powerfu…

【C++】类和对象<上>(类的定义,类域,实例化,this指针)

目录 一. 类的定义 【对比c】结构体和类的区别 1. 称呼:变量 or 对象? 2. 类型: 3. 访问限定: 4. c和c结构体使用 5. 相同点: 二. 类域 三. 实例化 1. 1对N 2. 计算大小只考虑成员变量 3. 到此一游 四. …