Java多线程的单例设计模式 多种实现方法

目录

前言

饿汉式

懒汉式

Double_check

 volatile + double_check

Holder方式

 枚举


前言

        单例设计模式GOF23中设计模式中最常用的设计模式之一, 单例设计模式提供了多线程环境下的保证唯一实例性的解决方案, 虽然简单, 但是实现单例模式的方式多种多样, 因此需要从多个维度去评价: 

  • 线程安全
  • 性能
  • 懒加载

懒加载的好处是啥? 

提高页面加载速度:通过仅在需要时加载资源,懒加载可以减少初次加载时的资源量,使页面更快地呈现给用户。

节省带宽:只有当用户真正需要时才加载资源,这样可以节省不必要的带宽使用,特别是对于那些需要大量数据或高分辨率图片的应用。

提升用户体验:通过延迟加载,可以让用户更快地看到页面的主要内容,从而提高用户的满意度和体验。

减少服务器负载:懒加载可以减少一次性加载大量资源的需求,从而减少服务器的负载,优化服务器性能。

优化移动端性能:对于移动设备,网络连接通常较慢且不稳定,懒加载可以显著提高移动端的页面加载速度和性能。

节省系统资源:懒加载可以避免不必要的资源消耗,减少浏览器和设备的内存使用,尤其是在处理大量数据或复杂页面时。

饿汉式

public class TestMain {// 定义的时候直接初始化private static TestMain instance = new TestMain ();// 提供get方法public static TestMain getInstance() {return instance;}// 私有化构造方法private TestMain() {}
}

        在类加载过程中提到过, instance作为类变量, 在类的初始化的过程中, 会被收集到<cinit>()反方法中, 该方法每个类只会执行一次, 100%保证同步, 因此在使用instance的过程中, instance不可能会被实例化两次

        但是, 这也正是因为是在类初始化的时候就已经存在, 那么就表明, 这个instance实例在完成初始化之后, 并存储在内存空间之后, 并不会立即就会被使用到, 那么就会在内存中驻留一个时间间隔, 这个时间间隔带来的成本增加需要通过量化, 不能一概而论

        例如如果instance实例的内存消耗小, 那么饿汉式也未免不是一种良好的解决方案, 虽然他不支持懒加载.

        对于这个getInstance方法, 没有加任何锁, 因此没有其他的性能消耗, 速度快

懒汉式

        所谓懒汉, 就是我很懒, 我只有在使用类实例的时候才去创建, 可以避免类在初始化的时候就提前去创建.

代码如下: 

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

        当前方法的getInstace, 每次进入这个方法之前都会获取这个类的Class对象, 然后加锁, 但是我每次调用getIsntace都需要拿到一次锁, 这样性能不高. 同一个时刻只能由一个线程能拿到这个instance实例. 

为了避免上述情况, 我们考虑降低这个锁的粒度: 

你似乎可以这样写: 

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

        但是这样写, 好像每次getInstance获取实例还是需要拿到锁, 还是会产生额外的锁竞争消耗 , 但是你仔细分析其实不难得知, 我除了第一次拿的时候需要初始化, 其他的时候, 都不需要初始化, 直接返回就行了, 于是你写出了这样的代码: 

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

但是第一次的时候会存在多线程风险, 也就是可能会存在多次给instance赋值的情况.... 

于是为了线程安全, 就给他加了锁, 如下: 

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

 但是这样似乎并不安全, 如果有两个线程都进入了这个if判断中, 同时判断了这个instance为null都准备拿锁进行实例化, 假设是线程1拿到锁, 之后instance 被初始化, 然后被返回, 释放锁后, 线程2又走了一遍实例化的过程, 因此这个instance就被实例化了两次, 也就没有保障单列模式. 

于是就有了下面这种写法. 

Double_check

        不再在方法上加锁, 我们在方法内部使用锁块来进行加锁, 降低粒度. 如下: 

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

 volatile + double_check

public final class Test {private static Test instance = null;Object var1;Object var2;private Test() {}public static Test getInstance() {if (instance == null) {synchronized (Test.class) {if (instance == null)instance = new Test ();}}return instance;}public static void setInstance(Test instance) {Test.instance = instance;}public Object getVar1() {return var1;}public Object getVar2() {return var2;}}

但是同时这个方法还存在一个问题, 都知道java的内存模型中每个线程都有自己的工作内存,. 变量都是暂存在工作内存中, 对工作内存中修改变量, 如果没有的就去内公共内存中去读取, 那么档期使用这种方法更新的时候, 其实其他线程是看不到第一个线程对其的修改. 

除此之外, 上述的doublecheck也不一定安全, 看看上面的一个案例

        在没有volatile的情况下,编译器可能会将instance = new Test();这行代码拆分为多个操作:1) 分配内存给instance;2) 调用Test的构造函数来初始化对象;3) 将instance指向分配的内存。如果没有适当的同步措施,这些操作的执行顺序可能会被重排序,比如先执行步骤1和3,再执行步骤2。这样,在步骤2完成之前,其他线程就可能看到非空的instance引用,但此时对象可能还未被完全初始化,从而导致程序出错。 

代码如下: 

public final class Test {private volatile static Test instance = null;Object var1;Object var2;private Test() {}public static Object getInstance() {if (instance == null) {synchronized (Test.class) {if (instance == null)instance = new Test();}}return instance;}public static void setInstance(Test instance) {Test.instance = instance;}public Object getVar1() {return var1;}public Object getVar2() {return var2;}}

Holder方式

在讲解java的jvm类加载机制的时候提到过: 

可以利用这个特性, 在需要实现单例模式的类中 添加一个静态内部类, 此时只要使用到了这个静态内部类里面的静态变量, 就会执行这个类的初始化, 但是后面无论如何 访问多少次, 就不再初始化了, 这就保证了单例模式的线程安全, 同时实现了懒加载, 重复获取也没有锁竞争. 

public final class Test {private Test() {}private static class Holder {private static final Test INSTANCE = new Test();}public static Test getInstance() {return Holder.INSTANCE;}}

 枚举

        effect java力荐方式: 

在Java中,使用枚举(Enum)来实现单例模式是一种既简单又高效的方法。枚举类型在Java中是一种特殊的类,它默认就是单例的,并且线程安全。这是因为JVM保证了枚举实例的唯一性,并且在枚举的任何地方访问枚举实例时,都不需要同步

public enum SingletonEnum {  INSTANCE;  // 可以在这里添加方法  public void doSomething() {  System.out.println("Doing something...");  }  // 示例:添加私有字段和方法  private Singleton instance;  public void setData(Singleton instance) {  this.instance= instance;  }  public Singleton getData() {  return instance;  }  }

其中: 

SingletonEnum instance1 = SingletonEnum.INSTANCE;  
SingletonEnum instance2 = SingletonEnum.INSTANCE;  

instance1和instance2是相同的实例.

当然这种方式不是懒加载的, 如果你想使用懒加载, 那么你可以使用静态内部类类似的加载机制: 

java的枚举类型和静态内部类一样, 只有在第一次被引用时会被加载和初始化,因此 EnumHolder 只有在调用 getInstance() 方法时才会被加载

public final class Test {private Test() {}private enum EnumHolder {INSTANCE;private Test test;EnumHolder() {test = new Test();}private Test getTest() {return test;}}// 调用该方法会主动使用 EnumHolder.INSTANCE, EnumHolder.INSTANCEpublic static Test getInstance() {return EnumHolder.INSTANCE.getTest();}}

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

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

相关文章

[安洵杯 2019]easy_serialize_php

[安洵杯 2019]easy_serialize_php [安洵杯 2019]easy_serialize_php - DGhh - 博客园 (cnblogs.com) [安洵杯 2019]easy_serialize_php - 何止(h3zh1) - 博客园 (cnblogs.com) 涉及的考点是字符串逃逸 <?php //GET一个f $function $_GET[f];//定义过滤的字符串数组 fu…

c++初阶-------模板

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

科普文:微服务之全文检索SpringBoot整合ElasticSearch说明

一、RestHighLevelClient介绍 JavaREST客户端有两种模式&#xff1a; Java Low Level REST Client&#xff1a;ES官方的低级客户端。低级别的客户端通过http与Elasticsearch集群通信。Java High Level REST Client&#xff1a;ES官方的高级客户端。基于上面的低级客户端&…

Io 35

FIleinputStream字节输入 package File.io;import java.io.*;public class io1 {public static void main(String[] args) throws IOException {// InputStream is new FileInputStream(new File("C:\\Users\\SUI\\Desktop\\Java1\\one\\src\\kaishi"));//简化Input…

C++ 几何算法 - 求两条直线交点

一&#xff1a;算法介绍 1. 首先定义两条直线方程&#xff1a; 2. 解方程&#xff0c;求出x, y坐标 3. 如果x分母的行列式等于0&#xff0c; 说明两条直线平行或方向相反 4. 如果x&#xff0c;y分母的行列式都等于0&#xff0c;说明两条线重叠 二&#xff1a;代码实现: #inclu…

求职Leetcode题目(5)

1.分割回文串 每一个结点表示剩余没有扫描到的字符串&#xff0c;产生分支是截取了剩余字符串的前缀&#xff1b;产生前缀字符串的时候&#xff0c;判断前缀字符串是否是回文。如果前缀字符串是回文&#xff0c;则可以产生分支和结点&#xff1b;如果前缀字符串不是回文&#…

Vue常见问题(一)组件的使用

Failed to resolve component. 报错原因&#xff1a; 组件注册错误&#xff1a;我们在组件中使用了未注册的组件。在Vue中&#xff0c;组件必须先注册才能使用。 解决方法&#xff1a; 引用组件 &#xff1a; import ItemPage from "/components/itemPage.vue";…

【踩坑】pytorch中的索引与copy_结合不会复制数据及其解决方案

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 背景知识 实验验证 结论分析 错误案例 处理方法 注意事项 附加说明 基本索引返回视图 高级索引返回副本 赋值操作都是原地操作 以下内容…

重生之我 学习【数据结构之顺序表(SeqList)】

⭐⭐⭐ 新老博友们&#xff0c;感谢各位的阅读观看 期末考试&假期调整暂时的停更了两个多月 没有写博客为大家分享优质内容 还容各位博友多多的理解 美丽的八月重生之我归来 继续为大家分享内容 你我共同加油 一起努力 ⭐⭐⭐ 数据结构将以顺序表、链表、栈区、队列、二叉树…

索尼相机SD卡找不到视频怎么办?提供全面解决方案

在使用索尼相机拍摄美好瞬间时&#xff0c;SD卡作为存储介质&#xff0c;承载着珍贵的视频和照片。然而&#xff0c;有时我们可能会遇到SD卡中视频文件无法找到的问题&#xff0c;这无疑让人倍感焦虑。本文旨在为大家提供一套全面的解决方案&#xff0c;希望帮助大家快速找回丢…

探索Linux世界之Linux环境开发工具的使用

一、yum -- Linux软件包管理器 1、什么是yum yum(Yellow dog Updater, Modified)&#xff1a; 是Linux下非常常用的一种包管理器. 主要应用在Fedora, RedHat, Centos等发行版上。 在Linux上安装软件的方式&#xff1a; 源代码直接安装&#xff1a;在Linux下安装软件, 一个通…

The Llama 3 Herd of Models 第8部分语音实验部分全文

第1,2,3部分,介绍、概览、预训练 第4部分,后训练 第5部分,结果 第6部分,推理 第7部分,视觉实验 8 Speech Experiments 我们进行了实验来研究将语音功能集成到Llama 3中的组合方法,类似于我们用于视觉识别的方法。在输入端,一个编码器,连同一个适配器,被并入处理语…

uniapp vue3 转换华为鸿蒙(以及问题一些解决方案)

主要是从 Windows系统配置 、配置离线SDK和DevEco-Studio、HBuilderX、三方面进行配置。 因为我也是之前写小程序的用uniapp vue3 写的看官网&#xff08;uni-app 开发鸿蒙应用 | uni-app官网&#xff09;的时候看到vue3 uniapp 写法可以转换华为鸿蒙开发&#xff0c;我就自己来…

为什么要用分布式锁

单应用中,如果要确保多线程修改同一个资源的安全性 加synchronized就可以了 但是性能不高 而mybatis-plus的乐观锁就可以很好的解决这类问题 但是这样的锁机制,只在单应用中有效 试想,在分布式下,有没有可能出现多个应用中的线程同时去修改同一个数据资源的并发问题 例如A …

Rstudio Server常见问题处理手册

一.开头 上面这个界面是不是非常熟悉&#xff1f;Rstudio 死亡圈圈一般发生在输入账号密码后进入Rstudio的时候&#xff0c;如果之前运行过大任务&#xff0c;有可能会出现这种情况。Rstudio常见问题我们如何排查和处理,本文章将给你一些思路和处理方式。 【ads】如果您不想被…

【开源】嵌入式Linux(IMX6U)应用层综合项目(4)--音乐播放器APP

1.简介 此文章并不是教程&#xff0c;只能当作笔者的学习分享&#xff0c;只会做一些简单的介绍&#xff0c;其他的各位结合着代码和运行现象自己分析吧&#xff0c;相信通过函数名和注释&#xff0c;基本上是不难看懂代码的&#xff0c;其中涉及到的一些技术栈&#xff0c;也…

图论(强联通分量)

在图论中&#xff0c;特别是在讨论有向图&#xff08;Directed Graph&#xff09;时&#xff0c;我们常常需要了解图的结构特性&#xff0c;比如强联通分量&#xff08;Strongly Connected Components, SCC&#xff09;。了解强联通分量中的各种边对于理解图的整体结构以及某些…

Redisson可重入锁原理(基于黑马视频总结,保姆级)

上一篇文章我们基于redis的set nx ex 命令以及Lua脚本实现了基本的分布式锁&#xff0c;但是还存在一下几点问题。于是又引出了redisson。 为什么基于SETNX的分布式锁无法实现可重入 先在method1中获取锁&#xff0c;获取成功后又调用method2&#xff0c;而method2内部也会获取…

spring+SSM+Mybatis面试题(上)(30道)

目录 1. 何为Spring Bean容器?Spring Bean容器与Spring IOC 容器有什么不同吗?2. Spring IOC 如何理解?3. Spring DI 如何理解?4. Spring 中基于注解如何配置对象作用域?以及如何配置延迟加载机制?1.配置作用域需要注解Scope(“Singleton”)2.开启延迟加载&#xff1a;La…

脚本:自动生成精准的Oracle AWR报告

很多朋友把AWR报告发过来让我帮忙分析Oracle数据库的性能&#xff0c;但很多报告都有一个共同的缺陷&#xff1a;就是这些报告覆盖的时间范围太广&#xff0c;导致性能问题的数据被严重稀释。 英文原文&#xff1a;Script: Generating Focused AWR Reports 为了解决这个问题&a…