单例模式懒汉式、饿汉式(线程安全)

  • 饿汉式单线程安全吗

饿汉式单例(Eager Singleton)是线程安全的。这种实现方式在类加载时就创建了单例实例,因此在多线程环境中,不存在多个线程同时创建实例的问题。

饿汉式单例的实现

以下是一个饿汉式单例的示例:

public class EagerSingleton {// 在类加载时立即创建实例private static final EagerSingleton instance = new EagerSingleton();// 私有构造函数private EagerSingleton() {// Initialization code}// 提供全局访问点public static EagerSingleton getInstance() {return instance;}
}

线程安全的原因

  1. 提前实例化:通过在类加载时创建实例,确保了在任何线程访问 getInstance() 方法之前,实例已经被创建。

  2. 无同步开销:由于实例在类加载时就已创建,后续的访问不需要任何同步操作,提高了性能。

适用场景

饿汉式单例适合以下场景:

  • 需要确保一定会使用该实例,并且不怕在启动时占用内存。
  • 在应用启动时就能确定实例的创建。

缺点

饿汉式单例的缺点在于:

  • 可能导致资源浪费:如果实例在运行时并不一定会被使用,它仍然会在启动时占用内存。
  • 不支持懒加载:无论是否需要,实例都会在类加载阶段创建。

总之,饿汉式单例是线程安全的,适合特定场景,但在设计时需要根据需求仔细选择实现方式。


  • 懒汉式加载时线程安全的吗?

懒汉式单例(Lazy Singleton)在默认实现中并不是线程安全的。多个线程同时访问 getInstance() 方法时,可能会导致多个实例的创建。为了确保线程安全,需要采取适当的同步措施。

懒汉式单例的基本实现

以下是一个不安全的懒汉式单例示例:

public class LazySingleton {private static LazySingleton instance;private LazySingleton() {// Initialization code}public static LazySingleton getInstance() {if (instance == null) {instance = new LazySingleton(); // 可能会导致多个线程创建多个实例}return instance;}
}

线程不安全的原因

  • 竞态条件:多个线程同时调用 getInstance() 方法时,可能会同时发现 instancenull,从而各自创建新的实例,导致多个实例存在。

确保线程安全的方式

  1. 使用 synchronized 关键字
public class LazySingleton {private static LazySingleton instance;private LazySingleton() {// Initialization code}public static synchronized LazySingleton getInstance() {if (instance == null) {instance = new LazySingleton();}return instance;}
}

这种方式确保了在同一时刻只有一个线程可以执行 getInstance() 方法,避免了多个实例的创建。

  1. 双重检查锁定(Double-Checked Locking)
public class LazySingleton {private static volatile LazySingleton instance;private LazySingleton() {// Initialization code}public static LazySingleton getInstance() {if (instance == null) {synchronized (LazySingleton.class) {if (instance == null) {instance = new LazySingleton();}}}return instance;}
}

volatile 关键字的作用:

  • 防止指令重排序:在没有 volatile 的情况下,JVM 可能会对对象的初始化过程进行优化,导致指令重排序。例如,在创建对象时,可能会先返回对象引用,然后再初始化对象。这将导致某些线程可能获取到尚未完全初始化的实例。
    确保可见性:volatile 关键字确保了当一个线程修改 instance 时,其他线程能够立即看到这个变化。这避免了由于线程缓存导致的可见性问题。
  • 优点:
    性能优化:使用双重检查锁定可以避免每次调用 getInstance() 时都进行同步,只有在 instance 为 null 时才会加锁。这样在多次调用时,性能开销显著降低。
    延迟初始化:实例仅在第一次调用时创建,避免了实例的早期创建,节省资源。
    适用场景:
    适合需要懒加载的单例模式,特别是在高并发的环境下,能够有效地减少锁的开销。

3.通过静态内部类实现,利用 Java 的类加载机制确保线程安全。

public class Singleton {private Singleton() {// Initialization code}private static class SingletonHelper {private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return SingletonHelper.INSTANCE;}
}

使用静态内部类来实现单例模式是一种优雅且高效的方法。以下是这种实现方式的一些主要优点:

    1. 线程安全
      类加载机制:静态内部类的实例是在第一次调用 getInstance() 方法时加载的,这意味着在类加载过程中不会创建实例,从而确保了线程安全。
      避免同步开销:由于实例在静态内部类中创建,只有在需要时才会被加载,因此不需要在方法上添加同步锁,从而减少了性能开销。
    1. 延迟初始化
      按需创建:实例仅在第一次调用 getInstance() 时创建,这样可以避免在应用启动时就创建资源,节省了内存和其他资源的使用。
    1. 避免反序列化问题
      反序列化保护:如果实现了 Serializable 接口,静态内部类的单例实现可以防止通过反序列化创建多个实例。因为反序列化时,静态内部类的静态字段会被正确初始化,确保返回的仍然是同一个实例。
    1. 简单易读
      代码清晰:静态内部类的实现方式简洁明了,易于理解和维护。相较于其他实现方式(如双重检查锁定),代码量较少,逻辑更加直观。
    1. 兼容性
      Java 语言特性:这种实现方式利用了 Java 的类加载机制,是一种符合 Java 语言设计的优雅方案,能够很好地与其他 Java 特性结合使用。

总结

默认的懒汉式单例实现是线程不安全的。要确保线程安全,可以使用同步机制或其他设计模式。推荐静态内部类来实现


以下是使用静态内部类实现的单例模式的示例,包括一个 main 函数,展示如何调用并验证单例的行为。

单例模式实现

public class Singleton {private Singleton() {// Initialization code}private static class SingletonHelper {private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return SingletonHelper.INSTANCE;}
}

主函数示例

public class Main {public static void main(String[] args) {// 创建多个线程来测试单例Runnable task = () -> {Singleton instance = Singleton.getInstance();System.out.println("Instance HashCode: " + instance.hashCode());};// 启动多个线程Thread thread1 = new Thread(task);Thread thread2 = new Thread(task);Thread thread3 = new Thread(task);thread1.start();thread2.start();thread3.start();// 等待线程执行完成try {thread1.join();thread2.join();thread3.join();} catch (InterruptedException e) {e.printStackTrace();}// 主线程再次获取实例Singleton mainInstance = Singleton.getInstance();System.out.println("Main Thread Instance HashCode: " + mainInstance.hashCode());}
}

示例说明

  1. 单例类Singleton 类使用静态内部类实现了单例模式,确保了线程安全和延迟初始化。

  2. 主函数

    • 定义了一个 Runnable 任务,任务中调用 Singleton.getInstance() 并打印实例的哈希码。
    • 启动了三个线程来并发访问单例实例。
    • 主线程最后再次调用 getInstance() 并打印该实例的哈希码。

运行结果

你应该会看到所有线程打印的哈希码相同,表明它们获取的是同一个实例。例如:

Instance HashCode: 123456789
Instance HashCode: 123456789
Instance HashCode: 123456789
Main Thread Instance HashCode: 123456789

总结

这个示例展示了如何使用静态内部类实现单例模式,并通过多线程验证了其线程安全性。所有线程和主线程都获取到了同一个实例,验证了单例模式的有效性。

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

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

相关文章

大模型的实践应用33-关于大模型中的Qwen2与Llama3具体架构的差异全解析

大家好,我是微学AI,今天给大家介绍一下大模型的实践应用33-关于大模型中的Qwen2与Llama3具体架构的差异全解析。Qwen2模型与Llama3模型在架构上存在一些细微的差异,这些差异主要体现在注意力机制、模型尺寸相关参数以及嵌入层处理等方面。以下是对这些差异的详细分析。 文章…

NAT 技术如何解决 IP 地址短缺问题?

NAT 技术如何解决 IP 地址短缺问题? 前言 这是我在这个网站整理的笔记,有错误的地方请指出,关注我,接下来还会持续更新。 作者:神的孩子都在歌唱 随着互联网的普及和发展,IP 地址的需求量迅速增加。尤其是 IPv4 地址&…

kafka的备份策略:从备份到恢复

文章目录 一、全量备份二、增量备份三、全量恢复四、增量恢复 前言:Kafka的备份的单元是partition,也就是每个partition都都会有leader partiton和follow partiton。其中leader partition是用来进行和producer进行写交互,follow从leader副本进…

使用sam进行零样本、零学习的分割实践

参照:利用SAM实现自动标注_sam标注-CSDN博客,以及SAM(分割一切模型)的简单调用_sam使用-CSDN博客 sam简介: Segment Anything Model(SAM)是Meta公司于2023年发布的一种AI模型,它打破…

【Git】—— 使用git操作远程仓库(gitee)

目录 一、远程仓库常用命令 1、从远程仓库克隆项目 2、查看关联的远程仓库 3、添加关联的远程仓库 4、移除关联的远程仓库 5、将本地仓库推送到远程仓库 6、从远程仓库拉取项目 二、分支命令 1、查询分支 2、创建分支 3、切换分支 4、推送到远程分支 5、合并分支 …

攻防世界web新手第五题supersqli

这是题目,题目看起来像是sql注入的题,先试一下最常规的,输入1,回显正常 输入1‘,显示错误 尝试加上注释符号#或者–或者%23(注释掉后面语句,使1后面的单引号与前面的单引号成功匹配就不会报错…

【MySQL】SQL 优化经验

1. 表的设计优化 参考依据:参考阿里开发手册嵩山版,其中有很多关于MySQL表设计的内容。类型选择:根据存储内容选择合适类型,如数值存储可选tinyint、bigint等,字符串可选varchar或text,根据内容长短选择合…

使用 .NET 6 或 .NET 8 上传大文件

如果您正在使用 .NET 6,并且它拒绝上传大文件,那么本文适合您。 我分享了一些处理大文件时需要牢记的建议,以及如何根据我们的需求配置我们的服务,并提供无限制的服务。 本文与 https://blog.csdn.net/hefeng_aspnet/arti…

STM32使用UART发送字符串与printf输出重定向

首先我们先看STM32F103C8T6的电路图 由图可知,其PA9和PA10引脚分别为UART的TX和RX(注意:这个电路图是错误的,应该是PA9是X而PA9是RX,我们看下图的官方文件可以看出),那么接下来我们应该找到该引脚的定义是什么&#xf…

转运机器人推动制造业智能化转型升级

​在当今制造业智能化转型的浪潮中,技术创新成为企业脱颖而出的关键。富唯转运机器人凭借一系列先进技术,成为智能转型的卓越之选。 一体化 AMR 控制系统是富唯的一大亮点。它采用低代码流程搭建和配置模式,极大地缩短了部署时间。企业无需耗…

深度分析java 使用 proguard 如何解析混淆后的堆栈

经过proguard混淆过后,发生异常时堆栈也进行了混淆,那么如果获取的原始的堆栈呢?我们下面来看下 使用proguard 根据mapping文件直接解析 import proguard.obfuscate.MappingReader; import proguard.retrace.FrameInfo; import proguard.re…

基于JAVA+SpringBoot+Vue的影院订票系统

基于JAVASpringBootVue的影院订票系统 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末附源码下载链接🍅 哈喽兄…

LeetCode 83 :删除排链表中的重复元素

题目: 地址:https://leetcode.cn/problems/remove-duplicates-from-sorted-list/ 方法一: 方法二: package com.zy.leetcode.LeetCode_04;/*** Author: zy* Date: 2024-12-25-15:19* Description: 删除排链表中的里复元素* …

金仓数据库-用户与角色对象权限访问的查看

数据库用户 创建用户 创建用户且设置密码 create user user01 password 123;\du 查看用户user01,可以看见创建成功 创建用户设置密码和不可继承 create user02 password 123 noinherit;修改用户的属性 设置用户的连接数 设置为1个 alter user user01 connect…

理解神经网络

神经网络是一种模拟人类大脑工作方式的计算模型,是深度学习和机器学习领域的基础。 基本原理 神经网络的基本原理是模拟人脑神经系统的功能,通过多个节点(也叫神经元)的连接和计算,实现非线性模型的组合和输出。每个…

联通光猫怎么自己改桥接模式?

环境: 联通光猫 ZXHN F677V9 硬件版本号 V9.0 软件版本号 V9.0.0P1T3 问题描述: 联通光猫怎么自己改桥接模式 家里用的是ZXHN F677V9 光猫,最近又搞了个软路由,想改桥接模式 解决方案: 1.拿到最新超级密码&…

Matrix-Breakout 2 Morpheus(找到第一个flag)

第一步 信息收集 (1)寻找靶场真实ip arp-scan -l 靶场真实 ip 为192.168.152.154 (2)探测端口及服务 nmap -p- -sV 192.168.52.135 第二步 开始渗透 (1)访问web服务 http://192.168.152.154and http://192.168.52.135:81 发现 81 端口的页面要登录 我们使用 dirb 扫描…

【CSS in Depth 2 精译_094】16.2:CSS 变换在动效中的应用(下)——导航菜单的文本标签“飞入”特效与交错渲染效果的实现

当前内容所在位置(可进入专栏查看其他译好的章节内容) 第五部分 添加动效 ✔️【第 16 章 变换】 ✔️ 16.1 旋转、平移、缩放与倾斜 16.1.1 变换原点的更改16.1.2 多重变换的设置16.1.3 单个变换属性的设置 16.2 变换在动效中的应用 16.2.1 放大图标&am…

机器人C++开源库The Robotics Library (RL)使用手册(三)

进入VS工程,我们先看看这些功能函数及其依赖库的分布关系: rl命名空间下,主要有八大模块。 搞定VS后将逐个拆解。 1、编译运行 根据报错提示,配置相应错误的库(根据每个人安装位置不同而不同,我的路径如下:) 编译所有,Release版本耗时大约10分钟。 以rlPlan运动…