设计模式———单例模式

news2024/11/26 22:46:52

单例也就是只能有一个实例,即只创建一个实例对象,不能有多个。

可能会疑惑,那我写代码的时候注意点,只new一次不就得了。理论上是可以的,但在实际中很难实现,因为你无法预料到后面是否会脑抽一下~~因此我们就可以通过编码技巧,让编译器来帮助我们检查约束。

单例模式又分为两种:饿汉式和懒汉式区别在于实例对象创建的时机不同

0x00 饿汉式

说到饿汉,那一定是很饥饿的人,很急切,也就是说这个实例对象创建的时机比较早,在类加载的时候就创建了。

要点

  • 1、由于创建时机较早,设置为静态即可在类加载的时候就可以创建实例对象

  • 2、由于只能创建一个实例对象,因此将构造函数私有化

  • 3、对外提供一个获取实例对象的静态类方法

    public class Singleton{
        private static Singleton instance = new Singleton();
        
        public static Singleton getInstance(){
            return instance;
        }
         private Singleton(){
             
         }
    }

0x01 懒汉式

懒汉,顾名思义,很懒,比较缓慢,也就是说这个实例对象是在使用到的时候才去创建。

要点

  • 1、一开始设置为null,直到在使用的时候再去new实例对象

  • 2、由于只能创建一个实例对象,因此将构造函数私有化

3、对外提供一个获取实例对象的静态类方法

public class SingletonLazy {
    private static SingletonLazy instance = null;

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

    private SingletonLazy(){

    }
}

0x02 单例模式与多线程

众所周知,许多在单线程环境下的代码一到多线程环境下就会出错,在此将对单例模式在多线程下的编写进行调整优化~

先来看看饿汉式的代码,我们发现这个实现的方式是在类加载的时候就去实例化对象了,而后续在多线程中掉用getInstance方法来获取实例对象的时候,只会进行返回操作,即”只读”因此多个线程在操作的时候是安全的

再来看看懒汉式,可以发现在调用getInstance方法的时候,会进行修改操作,如果恰巧有两个线程同时进入了if,然后就会进行new操作,那不就创建了多个实例对象了,如果恰巧每个对象不是很轻量,可能有很多属性,加载了几十G的数据到内存中......

因此解决方法是进行加锁操作,保证if和new操作是原子的

public class SingletonLazy {
    private static SingletonLazy instance = null;

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

    private SingletonLazy(){

    }
}

这样修改以后,就能保证if和new操作是一个整体,此时线程的安全问题就得到了改善。

但是这样写是否就真的完了呢?

答案:不是。因为我们是单例模式,所以只需要new一次之后,实例对象就不可能是null了,后续直接return对象就行了。假如我们现在已经创建好了一个对象,当后续有多个线程去调用getInstance方法的时候,需要去获取到这把锁才能进行返回操作,也就是我们的多线程变成了串行化,并发程度几乎没了~

那是否有办法,既可以保证代码线程安全,又不会对执行效率产生大的影响呢?

如下代码:

public 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语句。

分析

当有多个线程进入了第一个if语句,此时里面有一个加锁操作,这样可以保证只创建了一个实例对象。假设此时已经创建好了一个实例对象了,当下一次又有多个线程进来时,由于instance不为null,直接返回了,并不会进入到加锁操作,此时并发并没有再受到影响了,既保证了正确又保证了效率。

总结

在锁外面的if语句保证了是否要进行加锁操作,即如果还没有创建实例对象,此时线程由于需要进行修改操作要进行加锁才能保证安全,如果已经创建了实例对象,此时线程安全了就不用加锁了。

在锁里面的if语句是来保证只创建一个实例对象。

0x03 指令重排序

上述代码其实还不够安全,还存在指令重排序的问题。

指令重排序,本质是编译器为了提高执行效率,调整原有的代码执行顺序(前提是保持逻辑不变)。

接下来具体问题具体分析:

上述的代码中,出现了一个new操作,而new操作其实是分三步的,第一步,申请内存空间,第二步,在内存空间上构造对象,第三步,把内存地址赋值给对象引用。一般是按照1,2,3的顺序执行的,但是编译器优化过后,可能会按照1,3,2的顺序执行,在单线程环境下都可以,但是在多线程环境下则不是。如果线程1是按照1,3,2顺序执行完了3后,线程2此时返回了这个实例对象,但是此时这个对象还没有初始化呢,也就是说这是个非法对象,当我们的线程2去访问这个对象的属性或方法时,此时就是非法,会出错。此时我们还需要再对instance加一个限制操作,即用volatile来修饰instance。

最终代码

public class SingletonLazy {
    private static volatile 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/1604177.html

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

相关文章

北京InfoComm China 2024今日开幕:重新定义人机交互

2024年4月17日,北京——北京InfoComm China 2024展会在万众期待中于北京国家会议中心盛大开幕。这场为期三天的行业盛会,将持续至4月19日,为参观者们带来一场前所未有的创新技术与解决方案的盛宴。 本届展会作为亚太地区领先的专业视听与集成…

本地jar包手动添加到Maven仓库

步骤&#xff1a; 打开命令行窗口&#xff08;Windows&#xff09;或终端&#xff08;macOS/Linux&#xff09;。使用cd命令导航到包含jar文件的目录。执行以下命令&#xff0c;将jar文件添加到Maven本地仓库&#xff1a; mvn install:install-file -Dfile<jar文件名>.…

Swift Publisher 5 for mac:打造精美版面

Swift Publisher 5 for mac&#xff1a;打造精美版面 Swift Publisher 5是一款专业的版面设计和编辑工具&#xff0c;为Mac用户提供了强大的设计功能和直观的操作界面。以下是关于Swift Publisher 5的功能介绍&#xff1a; 直观易用的界面&#xff1a;用户能够轻松地使用Swift …

图书管理系统概述

自友图书馆管理系统解决方案适用于中小学、大中专院校以及企事业单位中小型图书馆的自动化管理需求&#xff0c;其功能覆盖了图书馆自动化集成管理业务流程所包括的所有环节。《图书馆管理系统》首先应该按照我国图书馆行业通用CNMARC格式及《中图法第四版》行业标准开发而成,支…

【网安小白成长之路】6.pikachu、sql-labs、upload-labs靶场搭建

&#x1f42e;博主syst1m 带你 acquire knowledge&#xff01; ✨博客首页——syst1m的博客&#x1f498; &#x1f51e; 《网安小白成长之路(我要变成大佬&#x1f60e;&#xff01;&#xff01;)》真实小白学习历程&#xff0c;手把手带你一起从入门到入狱&#x1f6ad; &…

57.网络游戏逆向分析与漏洞攻防-基础数据分析筛选-获取他人发来的聊天信息

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果 内容参考于&#xff1a;如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果&…

从二本调剂到上海互联网公司算法工程师:我的成长故事

探讨选择成为一名程序员的原因&#xff0c;是出于兴趣还是职业发展&#xff1f; 在这个科技飞速发展的时代&#xff0c;程序员这一职业无疑成为了许多人眼中的香饽饽。那么&#xff0c;是什么驱使着越来越多的人选择投身于这一行业呢&#xff1f;是出于对编程的热爱&#xff0…

计算机网络基础:宏观认识

目录 一、网络发展背景与基本概念 二、网络协议的意义与TCP/IP五层结构模型 三、网络传输的基本流程与封装分用 四、ip地址和mac地址 随着信息技术的飞速发展&#xff0c;计算机网络已经成为了现代社会不可或缺的一部分。无论是工作、学习还是娱乐&#xff0c;我们几乎都离…

MySql中truncate,delete有什么区别?什么情况下id会不会连续呢?

TRUNCATE和DELETE都是用来删除表中数据的SQL命令&#xff0c;但它们的工作方式和使用场景有所不同&#xff1a; DELETE命令&#xff1a;DELETE命令用于从表中删除一行、多行或所有行。你可以添加WHERE子句来指定要删除的行。例如&#xff0c;DELETE FROM table_name WHERE cond…

Java项目实现Excel导出(Hutool)

官网&#xff1a; Excel生成-ExcelWriter (hutool.cn) 1.使用Hutool工具实现Excel导出&#xff08;.xlsx格式&#xff09; 业务场景&#xff1a; 使用SpringCloudmysqlmybatis-plus需要将数据库中的数据导出到Excel文件中 前端为Vue2 第零步&#xff1a;导入依赖 <!-…

[Win11·Copilot] Win11 系统更新重启后任务栏 Copilot 图标突然消失 | 解决方案

文章目录 前言Copilot介绍产生异常的原因解决方案总结 前言 在 Windows 11 的最新系统更新之后&#xff0c;一些用户报告了任务栏中 Copilot 图标消失的问题。这篇技术博文将为您提供详细的解决方案&#xff0c;帮助您恢复 Copilot 图标&#xff0c;并确保您能够继续享受 Copi…

计算机网络-IS-IS基础概念二

前面已经学习了IS-IS的定义、组成、NET地址标识以及路由器级别分类等&#xff0c;今天继续学习IS-IS基础概念知识。 参考链接&#xff1a;IS-IS路由协议基础概念 一、IS-IS支持的网络类型 IS-IS会自动根据接口的数据链路层封装决定该接口的缺省网络类型&#xff0c; IS-IS支持两…

list基础知识

list 1.list 的定义和结构 list 是双向链表&#xff0c;是C的容器模板&#xff0c;其接收两个参数&#xff0c;即 list(a,b) 其中 a 表示指定容器中存储的数据类型&#xff0c;b 表示用于分配器内存的分配器类型&#xff0c;默认为 list <int>; list 的特点&#xff1a;…

机器学习和深度学习--李宏毅 (笔记与个人理解)Day 16

Day 16 deep Learning – 鱼与熊掌兼得 最近在减重&#xff0c; 昨天跑了个一公里&#xff0c;然后今天上午又打了个篮球&#xff0c;真是老胳膊老腿了&#xff0c;运动完给我困得不行 Review 见前面的笔记 这里说dl 会提供一个足够大的模型&#xff0c; 来使得Dall loss 足够小…

Mac 利用Homebrew安装JDK

一、安装JDK17 1.安装openjdk17 2.把homebrew安装的openjdk17软链接到系统目录&#xff1a; brew install openjdk17 sudo ln -sfn $(brew --prefix)/opt/openjdk17/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-17.jdk 一、检查是否安装成功 在Termina…

python使用uiautomator2操作真机(oppo a9x)

环境&#xff1a; python3.8.10&#xff0c;oppo a9x(6G,128g)&#xff0c;版本android 11。 之前写过文章&#xff1a; python使用uiautomator2操作真机&#xff08;荣耀10青春版&#xff09;_python uiautomator2 控制真机-CSDN博客 python使用uiautomator2操作真机&…

每日两题 / 189. 轮转数组 560. 和为 K 的子数组(LeetCode热题100)

189. 轮转数组 - 力扣&#xff08;LeetCode&#xff09; 向右轮转将使尾部k个元素顶到头部 将整个数组反转&#xff0c;再分别反转前k个元素和剩下的元素即可 class Solution { public:void rotate(vector<int>& nums, int k) {k % nums.size();reverse(nums.begi…

RHCE2

一.配置server主机要求如下&#xff1a; 1.server主机的主机名称为 ntp_server.example.com //修改主机名 hostnamectl set-hostname ntp_server.example.comreboot 修改主机名后一定要记住重启。 2.server主机的IP为&#xff1a; 172.25.254.100 //主机ip配置 [rootntpse…

Java实现生成中间带图标的二维码

Java实现生成中间带图标的二维码 生成Base64格式的二维码&#xff0c;返回html渲染 package your.package;import com.google.zxing.*; import com.google.zxing.client.j2se.MatrixToImageWriter; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcod…

【Go语言快速上手(二)】 分支与循环函数讲解

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Go语言专栏⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Go语言知识   &#x1f51d;&#x1f51d; Go快速上手 1. 前言2. 分支与循环2.1…