【设计模式】单例模式|饿汉模式|懒汉模式|指令重排序

news2025/4/22 10:23:23

目录

1.什么是单例模式?

2.如何保证单例?

3.两种写法

(1)饿汉模式(早创建)

 (2)懒汉模式(缓执行,可能不执行)

4.应用场景

🔥5.多线程中的单例模式

(1)加锁

(2)双重if

(3)volatie

6.指令重排序(小概率出现问题)

(1)什么是指令重排序?

 (2)以Instance = new SingletonLazy( );为例分析

(3)解决上述指令重排序问题

7.延伸:了解

🔥8.常见考察


1.什么是单例模式?

单例模式是最常见的设计模式之⼀

Q:啥是设计模式?

A:编程中典型场景的解决方案,设计模式好⽐象棋中的"棋谱".红⽅当头炮,⿊⽅⻢来跳.针对红⽅的⼀些⾛法,⿊⽅应招的时候有⼀些固定的套路.按照套路来⾛局势就不会吃亏

单例模式即某个类在进程中又能有唯一实例

  • 有且只有一个对象,不会new出来多个对象,这样的对象就是“单例”

2.如何保证单例?

(1)保证单例:instance只有唯一一个,初始化也只是执行一次的

  • 保证单例:instance只有唯一一个,初始化也只是执行一次的
    static修饰的,其实是“类属性”,就是在“类对象”上的,每个类的类对象在JVM中只有一个,里面的静态成员,只有一份
  • 后续需要使用这个类的实例,就可以直接通过getInstance来获取已经new好的这个,而不是重新new

(2) 核心操作:private:禁止外部代码来创建该类的实例

  • 怎么操作?类之外的代码,尝试new的时候,势必就要调用构造方法,由于构造方法私有,无法调动,就会编译出错

3.两种写法

(1)饿汉模式(早创建)

唯一实例创建时机非常早,类似于饿了很久的人,​看到了吃的就赶紧开始吃(急迫)

package thread;

//单例,饿汉模式
//唯一实例创建时机非常早,类似于饿了很久的人,看到了吃的就赶紧开始吃(急迫)
class Singleton {
    private static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }

    private Singleton() {

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

        //Singleton s3 = new Singleton();
    }
}

 (2)懒汉模式(缓执行,可能不执行)

懒是提高效率,节省开销的体现 啥时候调用,就啥时候创建,如果不调用,就不创建了

package thread;

//单例模式,懒汉模式的实现
class SingletonLazy {
    private static SingletonLazy instance = null;

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

    private SingletonLazy (){

    }
}

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

        //SingletonLazy s3 = new SingletonLazy();
    }
}

Q:如果有多个线程同时调用getInstance,是否会产生线程安全问题?

A

  • 饿汉模式不存在线程安全问题,而懒汉模式存在
     
  • 饿汉模式:实例早就有了,每个线程getInstance,就是读取上面的静态变量,多个线程读取同一个变量,是线程安全的
  • 懒汉模式:instance = new SingletonLazy,赋值操作就是修改,而且操作不是原子的,肯定就是线程不安全的

图解:懒汉模式:非原子性操作

  • 还可能存在其他执行顺序,t2再次new,可能创建多个实例
  • instance只是一个引用,instance中地址指向的那个对象可能就是一个大对象,上述代码会出现覆盖,第二个对象的地址覆盖了第一个,进一步第一个对象没有引用指向了,就会被GC回收(但是这个创建时间的开销,是客观存在的)

4.应用场景

(1)写的服务器,要从硬盘上加载100G的数据到内存(加载到若干个哈希表中),肯定要写一个类,封装上述加载操作,并且获取一些获取、处理数据的业务逻辑

  • 代码中的有些对象,本身就不应该是多个实例的,从业务角度就应该是单个实例
    一个实例就管理100G的内存数据
  • 搞多个实例,就是N*100G的内存数据,机器肯定吃不消,没必要

(2)MySQL的配置文件,专门管理配置,需要加载配置数据到内存中提供其他代码使用,这样的类也是单例的

  • 如果是多个实例,就存储了多份数据,如果一样还罢了,如果不一样,以哪个为准?

🔥5.多线程中的单例模式

懒汉模式的线程安全问题如何解决呢?

(1)加锁

Q:这样加锁线程就安全了吗?

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

A: 没有,t2拿到锁后,还在直接执行new操作

图解:

应该把if和new操作打包成一个原子操作

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

(2)双重if

 加锁之后的问题Q:懒汉模式只有最开始调用getInstance会存在线程安全问题,一旦把实例创建好了,后续再调用,就是只读操作,就不存在线程安全问题了,但是上述代码,只要一调用getInstance方法,就需要先加锁,再执行后续操作(后续没有线程安全问题,还要加锁,有开销

真正的解决A:再加一层判断

连续两次一样的条件判断(单线程中无意义,但是多线程含义就不一样)

第一层if:判定是否要加锁(new之前要加锁,new之后就不用加了)

第二层if:判断是否要创建对象

 指令级理解

 t2拿到锁,这个时候instance已经被t1修改了

(3)volatie

private static volatile SingletonLazy instance = null;

为了保证第一次线程修改,后续线程一定会读到,加上volatile【避免内存可见性+指令重排序问题

综上:懒汉模式的线程安全版

//单例模式,懒汉模式的实现
class SingletonLazy {
    private static volatile SingletonLazy instance = null;

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

    private SingletonLazy (){

    }
}

6.指令重排序(小概率出现问题)

编译器的优化策略有很多: ​

把读内存优化到读寄存器,指令重排序,循环展开,条件分支预测...

(1)什么是指令重排序?

编译器会在保证逻辑是等价的情况下,调整二进制指令的执行顺序,从而提高效率

  • 正常来说写的代码,最终会编译成为一系列的二进制指令,CPU会按照顺序,一条一条执行

 (2)以Instance = new SingletonLazy( );为例分析

  • 这行代码可以细分为三个步骤
    • 申请内存空间
    • 调用构造方法(对内存空间进行优化)
    • 把此时内存空间的地址,赋值给Instance引用
  • 在指令重排序的策略下,上述执行的过程,不一定是123,也可能是132(但是1一定先执行)
    • 132这样的执行顺序,就可能存在线程安全问题
  • 指令级理解

    • 3一旦执行完,就意味着instance就非null,但是指向的对象其实是一个未初始化的对象(里面的成员都是0)
    • 执行到t2的时候,instance已经非null了,这里的条件无法进行,直接返回未初始完毕的instance
    • 后续如果t2中还有其他逻辑,就会对未初始完毕的对象进行操作,这样存在严重的问题

(3)解决上述指令重排序问题

  • 加上volatile,主要是针对某个对象的读写过程中,不会出现重排序
    • 很多地方都能重排序,但是只是针对这一过程中
    • 这样t2线程读到的数据,一定是t1已经构造完毕的完整对象了(一定是123执行的对象)

7.延伸:了解

(1)单例模式要确保反射下安全,即使动用反射也无法破坏单例特性

(2)单例模式要确保序列化下安全,即使动用Java标准库的序列化机制,也无法破坏单例特性

  • enum类型的实例天然支持序列化和反序列化
  • 序列化:把对象转为二进制字符串

🔥8.常见考察

(1)为什么说饿汉式单例天生就是线程安全的?

实例早就有了,每个线程getInstance,就是读取上面的静态变量,多个线程读取同一个变量,是线程安全的

(2)传统的懒汉式单例为什么是非线程安全的?

instance = new SingletonLazy,赋值操作就是修改,而且操作不是原子的,肯定就是线程不安全的

(3)怎么修改传统的懒汉式单例,使其线程变得安全?

1.线程不安全的版本

2.加锁版本

3.加上双重if

4.最后加上volatile

(4)线程安全的单例的实现还有哪些,怎么实现?

静态内部类单例

public class StaticInnerClassSingleton {
    private StaticInnerClassSingleton() {}

    private static class SingletonHolder {
        private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
    }

    public static StaticInnerClassSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

 静态内部类在外部类加载时不会被加载,只有在调用 getInstance 方法时才会加载类,从而创建实例。类加载过程是线程安全的,所以这种方式实现了线程安全的单例,并且具有延迟加载的特性

(5)双重检查模式、Volatile关键字在单例模式中的应用

1.双重检查模式:

第一层if:判定是否要加锁(new之前要加锁,new之后就不用加了)

第二层if:判断是否要创建对象

2.Volatile关键字:避免内存可见性+指令重排序问题

(6)ThreadLocal在单例模式中的应用

public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = 
        new ThreadLocal<ThreadLocalSingleton>() {
            @Override
            protected ThreadLocalSingleton initialValue() {
                return new ThreadLocalSingleton();
            }
        };

    private ThreadLocalSingleton() {}

    public static ThreadLocalSingleton getInstance() {
        return threadLocalInstance.get();
    }
}

ThreadLocal 会为每个使用该实例的线程都提供一个独立的副本,每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。在单例模式中使用 ThreadLocal,可以保证每个线程都有自己的单例实例,适用于需要在每个线程中维护单例状态的场景 

(7)枚举式单例

public enum EnumSingleton {
    INSTANCE;

    public void doSomething() {
        System.out.println("Doing something...");
    }
}

 枚举式单例是实现单例模式的最佳方式之一。它是线程安全的,因为枚举类型的实例创建是由 JVM 保证线程安全的。而且枚举类型可以防止反序列化和反射攻击,因为 Java 规范中规定,枚举类型的 clone()、readObject()、readResolve() 等方法都不会破坏单例的唯一性。使用时可以直接通过 EnumSingleton.INSTANCE 来获取单例实例

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

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

相关文章

01. HarmonyOS应用开发实践与技术解析

文章目录 前言项目概述HarmonyOS应用架构项目结构Ability生命周期 ArkTS语言特性装饰器状态管理 UI组件与布局基础组件响应式布局样式与主题 页面路由与参数传递页面跳转参数接收 数据绑定与循环渲染数据接口定义循环渲染 条件渲染组件生命周期最佳实践与性能优化组件复用响应式…

【NLP 30、文本匹配任务 —— 传统机器学习算法】

目录 一、文本匹配任务的定义 1.狭义解释 2.广义解释 二、文本匹配的应用 1.问答对话 2.信息检索 3.文本匹配任务应用 三、智能问答 1.智能问答的基本思路 依照基础资源划分&#xff1a; 依照答案产出方式划分 依照NLP相关技术划分 四、智能问答的价值 1.智能客服 2.Faq知识库问…

爬虫Incapsula reese84加密案例:Etihad航空

声明: 该文章为学习使用,严禁用于商业用途和非法用途,违者后果自负,由此产生的一切后果均与作者无关 一、找出需要加密的参数 1.js运行 atob(‘aHR0cHM6Ly93d3cuZXRpaGFkLmNvbS96aC1jbi8=’) 拿到网址,F12打开调试工具,随便搜索航班,切换到network搜索一个时间点可以找…

【Vue教程】使用Vite快速搭建前端工程化项目 Vue3 Vite Node.js

??大家好&#xff01;我是毛毛张! ??个人首页&#xff1a; ??今天毛毛张分享的是关于如何快速??♂搭建一个前端工程化的项目的环境搭建以及流程?? 文章目录 1.前端工程化环境搭建?? 1.1 什么是前端工程化1.2 nodejs的简介和安装 1.2.1 什么是Nodejs1.2.2 如何安装…

如何将飞书多维表格与DeepSeek R1结合使用:效率提升的完美搭档

将飞书的多维表格与DeepSeek R1结合使用&#xff0c;就像为你的数据管理和分析之旅装上一台涡轮增压器。两者的合作&#xff0c;不仅仅在速度上让人耳目一新&#xff0c;更是将智能化分析带入了日常的工作场景。以下是它们如何相辅相成并改变我们工作方式的一些分享。 --- 在…

算数操作符、赋值操作符、单目操作符、强制类型转换

一、算术操作符&#xff08;、 -、 *、 /、 %&#xff09; • - * / %操作符都是双⽬操作符,有**两个操作数**的符号就叫做双目操作符 10 4| || | 操作数1 操作数2// - % / * 以此类推•操作符也被叫做&#xff1a;运算符 1. 符号、符号 - 和 符号* •…

为AI聊天工具添加一个知识系统 之133 详细设计之74通用编程语言 之4 架构及其核心

本篇继续讨论 通用编程语言。 说明&#xff1a;本阶段的所有讨论都是围绕这一主题展开的&#xff0c;但前面的讨论分成了三个大部分&#xff08;后面列出了这一段的讨论题目的归属关系&#xff09;-区别distinguish&#xff08;各别&#xff09;&#xff1a; 文化和习俗。知识…

RNN实现精神分裂症患者诊断(pytorch)

RNN理论知识 RNN&#xff08;Recurrent Neural Network&#xff0c;循环神经网络&#xff09; 是一种 专门用于处理序列数据&#xff08;如时间序列、文本、语音、视频等&#xff09;的神经网络。与普通的前馈神经网络&#xff08;如 MLP、CNN&#xff09;不同&#xff0c;RNN…

私有云基础架构

基础配置 使用 VMWare Workstation 创建三台 2 CPU、8G内存、100 GB硬盘 的虚拟机 主机 IP 安装服务 web01 192.168.184.110 Apache、PHP database 192.168.184.111 MariaDB web02 192.168.184.112 Apache、PHP 由于 openEuler 22.09 系统已经停止维护了&#xff…

rust学习笔记11-集合349. 两个数组的交集

rust除了结构体&#xff0c;还有集合类型&#xff0c;同样也很重要&#xff0c;常见的有数组&#xff08;Array&#xff09;、向量&#xff08;Vector&#xff09;、哈希表&#xff08;HashMap&#xff09; 和 集合&#xff08;HashSet&#xff09;字符串等&#xff0c;好意外呀…

超详细:数据库的基本架构

MySQL基础架构 下面这个图是我给出的一个MySQL基础架构图&#xff0c;可以清楚的了解到SQL语句在MySQL的各个模块进行执行过程。 然后MySQL可以分为两个部分&#xff0c;一个是server层&#xff0c;另一个是存储引擎。 server层 Server层涵盖了MySQL的大多数核心服务功能&am…

AI催化新一轮创业潮与创富潮:深圳在抢跑

作者&#xff1a;尺度商业大掌柜黄利明 2025年春节伊始至今&#xff0c;从DeepSeek R1开源模型持续引发全球围观&#xff0c;到腾讯混元Turbo S模型发布秀出了"秒回"绝活&#xff0c;再到国务院发布《新一代人工智能发展规划&#xff08;2025-2030&#xff09;》重磅…

Python:类型转换和深浅拷贝,可变与不可变对象

int()&#xff1a;转换为一个整数&#xff0c;只能转换由纯数字组成的字符串 浮点型强转整型会去掉小数点及后面的数&#xff0c;只保留整数部分 #如果字符串中有数字和正负号以外的字符就会报错 float()&#xff1a;整形转换为浮点型会自动添加一位小数 .0 如果字符串中有…

NAT 代理服务 内网穿透

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Linux 目录 一&#xff1a;&#x1f525; NAT 技术背景二&#xff1a;&#x1f525; NAT IP 转换过程三&#xff1a;&#x1f525; NAPT四&#xff1a;&#x1f525; 代理服务器&#x1f98b; 正向…

高级课第五次作业

首先配置交换机&#xff0c;路由器 LSW1配置 [SW1]vlan batch 10 20 30 40 [SW1]int g0/0/2 [SW1-GigabitEthernet0/0/2]port link-type access [SW1-GigabitEthernet0/0/2]port default vlan 10 [SW1]int g0/0/3 [SW1-GigabitEthernet0/0/3]port link-type access […

51单片机编程学习笔记——动态数码管显示多个数字

大纲 视觉残留原理生理基础神经传导与处理 应用与视觉暂留相关的现象 频闪融合不好的实现好的效果 延伸 在《51单片机编程学习笔记——动态数码管》一文中&#xff0c;我们看到如何使用动态数码管显示数字。但是基于动态数码管设计的特点&#xff0c;每次只能显示1个数字。这就…

金蝶ERP星空对接流程

1.金蝶ERP星空OPENAPI地址&#xff1a; 金蝶云星空开放平台 2.下载金蝶云星空的对应SDK包 金蝶云星空开放平台 3.引入SDK流程步骤 引入Kingdee.CDP.WebApi.SDK 右键项目添加引用&#xff0c;在打开的引用管理器中选择浏览页签&#xff0c;点击浏览按钮&#xff0c;找到从官…

【随手笔记】利尔达NB模组

1.名称 移芯EC6263GPP 参数 指令备注 利尔达上电输出 [2025-03-04 10:24:21.379] I_AT_WAIT:i_len2 [2025-03-04 10:24:21.724] LI_AT_WAIT:i_len16 [2025-03-04 10:24:21.724] [2025-03-04 10:24:21.733] Lierda [2025-03-04 10:24:21.733] [2025-03-04 10:24:21.745] OK移…

Vue3的核心语法【未完】

Vue3的核心语法 OptionsAPI与CompositionAPI Options API&#xff08;选项式&#xff09; 和 Composition API &#xff08;组合式&#xff09;是 Vue.js 中用于构建组件的两种不同方式。Options API Options API Options API 是 Vue 2 中的传统模式&#xff0c;并在 Vue 3…

解决redis lettuce连接池经常出现连接拒绝(Connection refused)问题

一.软件环境 windows10、11系统、springboot2.x、redis 6 7 linux&#xff08;centos&#xff09;系统没有出现这问题&#xff0c;如果你是linux系统碰到的&#xff0c;本文也有一定大参考价值。 根本思路就是&#xff1a;tcp/ip连接的保活(keepalive)。 二.问题描述 在spr…