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

目录

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>() {@Overrideprotected 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.mzph.cn/news/896963.shtml

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

相关文章

RocketMQ顺序消费机制

RocketMQ的顺序消费机制通过生产端和消费端的协同设计实现&#xff0c;其核心在于局部顺序性&#xff0c;即保证同一队列&#xff08;MessageQueue&#xff09;内的消息严格按发送顺序消费。以下是详细机制解析及关键源码实现&#xff1a; 一、顺序消费的核心机制 1. 生产端路…

【JavaEE】-- 多线程(初阶)4

文章目录 8.多线程案例8.1 单例模式8.1.1 饿汉模式8.1.2 懒汉模式 8.2 阻塞队列8.2.1 什么是阻塞队列8.2.2 生产者消费者模型8.2.3 标准库中的阻塞队列8.2.4 阻塞队列的应用场景8.2.4.1 消息队列 8.2.5 异步操作8.2.5 自定义实现阻塞队列8.2.6 阻塞队列--生产者消费者模型 8.3 …

【C++设计模式】第四篇:建造者模式(Builder)

注意&#xff1a;复现代码时&#xff0c;确保 VS2022 使用 C17/20 标准以支持现代特性。 分步骤构造复杂对象&#xff0c;实现灵活装配 1. 模式定义与用途 核心目标&#xff1a;将复杂对象的构建过程分离&#xff0c;使得同样的构建步骤可以创建不同的表示形式。 常见场景&am…

vuex中的state是响应式的吗?

在 Vue.js 中&#xff0c;Vuex 的 state 是响应式的。这意味着当你更改 state 中的数据时&#xff0c;依赖于这些数据的 Vue 组件会自动更新。这是通过 Vue 的响应式系统实现的&#xff0c;该系统使用了 ES6 的 Proxy 对象来监听数据的变化。 当你在 Vuex 中定义了一个 state …

若依框架中的岗位与角色详解

若依框架中的岗位与角色详解 一、核心概念与定位 岗位&#xff08;Post&#xff09; 业务职能导向&#xff1a;岗位是用户在组织架构中的职务标识&#xff08;如“开发人员”“项目经理”&#xff09;&#xff0c;用于描述工作职责而非直接控制权限。岗位与部门关联&#xff…

SQL经典常用查询语句

1. 基础查询语句 1.1 查询表中所有数据 在SQL中&#xff0c;查询表中所有数据是最基本的操作之一。通过使用SELECT * FROM table_name;语句&#xff0c;可以获取指定表中的所有记录和列。例如&#xff0c;假设有一个名为employees的表&#xff0c;包含员工的基本信息&#xf…

EP 架构:未来主流方向还是特定场景最优解?

DeepSeek MoE架构采用跨节点专家并行&#xff08;EP&#xff09;架构&#xff0c;在提升推理系统性能方面展现出巨大潜力。这一架构在发展进程中也面临诸多挑战&#xff0c;其未来究竟是会成为行业的主流方向&#xff0c;还是仅适用于特定场景&#xff0c;成为特定领域的最优解…

[密码学实战]Java实现国密(SM2)密钥协商详解:原理、代码与实践

一、代码运行结果 二、国密算法与密钥协商背景 2.1 什么是国密算法&#xff1f; 国密算法是由中国国家密码管理局制定的商用密码标准&#xff0c;包括&#xff1a; SM2&#xff1a;椭圆曲线公钥密码算法&#xff08;非对称加密/签名/密钥协商&#xff09;SM3&#xff1a;密码…

动漫短剧开发公司,短剧小程序搭建快速上线

在当今快节奏的生活里&#xff0c;人们的娱乐方式愈发多元&#xff0c;而动漫短剧作为新兴娱乐形式&#xff0c;正以独特魅力迅速崛起&#xff0c;成为娱乐市场的耀眼新星。近年来&#xff0c;动漫短剧市场呈爆发式增长&#xff0c;吸引众多创作者与观众目光。 从市场规模来看…

第四十五:创建一个vue 的程序

html <div id"app">{{ msg }}<h2>{{ web.title }}</h2><h3>{{ web.url }}</h3> </div> js /*<div id"app"></div> 指定一个 id 为 app 的 div 元素{{ }} 插值表达式, 可以将 Vue 实例中定义的数据在视图…

docer swarm集群部署springboot项目

1.准备两台服务器&#xff0c;安装好docker、docker-compose 因为用到了docker仓库&#xff0c;安装harbor,可以从github下载离线安装包 2. 我这边用到了gitlab-ci,整体流程也都差不多 1&#xff09;打包mvn clean install 2&#xff09;打镜像 docker-compose -f docker-compo…

Python测试框架Pytest的参数化

上篇博文介绍过&#xff0c;Pytest是目前比较成熟功能齐全的测试框架&#xff0c;使用率肯定也不断攀升。 在实际工作中&#xff0c;许多测试用例都是类似的重复&#xff0c;一个个写最后代码会显得很冗余。这里&#xff0c;我们来了解一下pytest.mark.parametrize装饰器&…

开发博客系统

前言 准备工作 数据库表分为实体表和关系表 第一&#xff0c;建数据库表 然后导入前端页面 创建公共模块 就是统一返回值&#xff0c;异常那些东西 自己造一个自定义异常 普通类 mapper 获取全部博客 我们只需要返回id&#xff0c;title&#xff0c;content&#xff0c;us…

【Spring Boot 应用开发】-05 命令行参数

Spring Boot 常用命令行参数 Spring Boot 支持多种命令行参数&#xff0c;这些参数可以在启动应用时通过命令行直接传递。以下是一些常用的命令行参数及其详细说明&#xff1a; 1. 基本配置参数 --server.port端口号 指定应用程序运行的HTTP端口&#xff0c;默认为8080。 jav…

20250304学习记录

第一部分&#xff0c;先来了解一下各种论文期刊吧&#xff0c;毕竟也是这把岁数了&#xff0c;还什么都不懂呢 国际期刊&#xff1a; EI收集的主要有两种&#xff0c; JA&#xff1a;EI源刊 CA&#xff1a;EI会议 CPCI也叫 ISTP 常说的SCI分区是指&#xff0c;JCR的一区、…

2024 年 MySQL 8.0.40 安装配置、Workbench汉化教程最简易(保姆级)

首先到官网上下载安装包&#xff1a;http://www.mysql.com 点击下载&#xff0c;拉到最下面&#xff0c;点击社区版下载 windows用户点击下面适用于windows的安装程序 点击下载&#xff0c;网络条件好可以点第一个&#xff0c;怕下着下着断了点第二个离线下载 双击下载好的安装…

网络安全检查漏洞内容回复 网络安全的漏洞

网络安全的核心目标是保障业务系统的可持续性和数据的安全性&#xff0c;而这两点的主要威胁来自于蠕虫的暴发、黑客的攻击、拒绝服务攻击、木马。蠕虫、黑客攻击问题都和漏洞紧密联系在一起&#xff0c;一旦有重大安全漏洞出现&#xff0c;整个互联网就会面临一次重大挑战。虽…

汽车智能钥匙中PKE低频天线的作用

PKE&#xff08;Passive Keyless Entry&#xff09;即被动式无钥匙进入系统&#xff0c;汽车智能钥匙中PKE低频天线在现代汽车的智能功能和安全保障方面发挥着关键作用&#xff0c;以下是其具体作用&#xff1a; 信号交互与身份认证 低频信号接收&#xff1a;当车主靠近车辆时…

uiautomatorviewer定位元素报Unexpected ... UI hierarchy

发现问题 借鉴博客 Unexpected error while obtaining UI hierarchy android app UI自动化-元素定位辅助工具 Unexpected error while obtaining UI hierarchy&#xff1a;使用uiautomatorviewer定位元素报错 最近在做安卓自动化,安卓自动化主要工作之一就是获取UI树 app端获…

通俗的方式解释“零钱兑换”问题

“零钱兑换”是一道经典的算法题目&#xff0c;其主要问题是&#xff1a;给定不同面额的硬币和一个总金额&#xff0c;求出凑成总金额所需的最少硬币个数。如果没有任何一种硬币组合能组成总金额&#xff0c;返回-1。 解题思路 动态规划&#xff1a;使用动态规划是解决零钱兑…