《javaEE篇》--单例模式详解

目录

单例模式

饿汉模式

懒汉模式

懒汉模式(优化) 

指令重排序

 总结

单例模式

单例模式属于一种设计模式,设计模式就好比是一种固定代码套路类似于棋谱,是由前人总结并且记录下来我们可以直接使用的代码设计思路。

单例模式就是,在有些场景中希望一个类只能有一个对象,不能有多个,这时你可能会觉得,这还不简单,我保证自己只new一个对象不就好了,但是你能保证自己只new一个对象,但是你能保证别人不会new对象吗?又或者,你真的能确保自己只new一个对象吗?所以“我保证自己只new一个对象不就好了”,这只是一个君子协定,光靠人是非常不靠谱的,所以我们要依靠计算机来帮助我们实现这个协定。

//这一点在很多场景上都需要. 比如 JDBC 中的 DataSource 实例就只需要一个.

单例模式的实现方式可以分为两种,饿汉模式懒汉模式

饿汉模式

具体实现方法

  1. 用static修饰,在类的内部创建一个现成的实例,让对象在在类加载时就被创建
  2. 提供一个方法当需要这个对象时就通过这个方法获得
  3. 用private修饰构造方法,这样外界就不能实例化这个类了

通过这三个方法就可以保证只有一个该类对象了

class Singleton{//创建时时机比较早(饿汉模式)//当类被加载时就会执行这里的创建实例操作private static Singleton instance = new Singleton();//后续需要这个对象,都通过这个方法获取public static Singleton getInstance(){return instance;}//私有构造方法private Singleton(){}
}
public class Demo17 {public static void main(String[] args) {//把构造方法设置为私有,之后就无法实例化这个对象了,这能通过刚刚创建的方法//Singleton singleton = new Singleton();Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();//这两个对象其实是一样的System.out.println(s1 == s2);}
}

 通过运行结果我们可以验证,上述在main方法中创建的对象是同一个,也就是刚刚在类中创建的那一个对象

这种方法会在类加载时就把对象创建好,如果不使用就会造成资源浪费

懒汉模式

具体实现

  1. 用static修饰,只声明类型创建实际对象
  2. 私有构造方法
  3. 提供外界用来获得对象的方法,设定当第一次调用方法时才创建对象
class SinngletonLazy{//在第一次调用时创建对象(懒汉模式)//只声明一个类型不创建实际对象private static SinngletonLazy instance = null;public static SinngletonLazy getInstance(){//如果是第一次调用,就创建实例对象,否则直接返回对象if(instance == null){instance = new SinngletonLazy();}return instance;}//私有构造方法private SinngletonLazy(){}
}

 饿汉模式和懒汉模式的最大区别就是,饿汉模式在类加载时就创建了对象,懒汉模式在第一次调用方法时才创建对象,假如我们要打开一本电子书,饿汉模式是要把整本书都加载完成才可以看,而懒汉模式则是在翻页时才加载下一页,可想而知懒汉模式是更高效的。

懒汉模式(优化) 

不过上述实现懒汉模式的方法只在单线程下才适用,如果是在多线程环境下会引起线程安全问题。

这里我来画图解释一下

如果当t1执行到①时,if判定为真,程序将要进到if语句内部,此时t2线程插了进来,由于t1线程还没创建出对象,所以t2线程的if语句也会判定为真,接着t2线程new了一个对象然后返回,最后又回到t1线程这边紧接着又new了一个对象,此时代码就出现问题了。

我们第一时间想到的方法就是直接给方法加上锁,这个方法确实可以解决问题,但是这样的话,代码的并发性就降低了,进而影响到代码的效率,所以我们要换种思路。

可以思考一下,我们的加锁操作只是再第一次创建对象时才需要,所以我们在加锁操作前再加一个if语句来判断当前是否需要加锁,这样就既实现了懒汉模式,又优化了代码的效率

class SinngletonLazy{//在第一次调用时创建对象(懒汉模式)//只声明一个类型不创建实际对象private static SinngletonLazy instance = null;public static SinngletonLazy getInstance(){//如果是第一次调用,就加锁创建对象,否则直接返回对象if(instance == null) {//判断是否需要加锁synchronized (SinngletonLazy.class){if (instance == null) {//判断是否需要new对象instance = new SinngletonLazy();}}}return instance;}//私有构造方法private SinngletonLazy(){}
}

 用简洁的话总结一下就是,因为为了防止资源浪费,所以使用一个if条件判断是否需要创建对象,因为在多线程下会出现线程安全的问题,所以要加锁,又因为只需要再第一次创建对象时才需要加锁,所以为了提升效率,再加一层if判断当前是否需要加锁(这两个if本质上没有什么关系,只是刚好判断条件一样而已)

这种方法也叫做Double Check(双重检验) + Lock(加锁) 

虽然当前的代码已经很完善了但是还是会有指令重排序的问题 

指令重排序

指令重排序也是一种编译器的优化,是编译器为了提高效率,在保证代码逻辑顺序不变的情况下,改变代码的实际顺序。

实际上这里的new操作可以分成三个步骤

  1. 向内存申请空间
  2. 在刚刚申请的空间上构造对象
  3. 把刚刚申请的空间的地址付给instance

 但是后面两个步骤对于编译器来说是可以颠倒的,按照123或者132来执行都是可以的,就看那种效率快,在单线程下是没有问题的但是在多线程下就会出现问题

假设有t1,t2线程,t1线程执行到new操作之后先执行1,3,此时编译器已经把刚刚申请的内存地址付给instance,Instance此时已经是一个非空的了,也就是说,此时instance指向一个还没有初始化的非法对象。但是这个时候还没有执行2操作,假如这时t2线程开始执行,判定第一个instance==Null,条件不成立(因为刚刚t1线程已经给instance赋值了),t2线程就直接会返回instance对象。但是此时,t1还没有到内存上构造出对象,T2线程的代码可能就会访问instance里面的属性了,进而就会引起一些bug。

 这里可能会有疑问了,刚刚不是给new操作加锁了吗?为什么还会再new操作时插入其他线程?这就是刚刚代码的缺陷之处,我们为了提高效率只是给new操作加锁了,但是此时t2线程只是执行到了第一个if条件,还没有涉及到任何锁操作,就更谈不上阻塞等待了。t2线程此时连第一个if条件都没有进去,就拿着一个还没有初始化的非法对象返回了。

解决方法

不过解决方法也很简单,使用volatile关键字修饰就可以了,volatile可以防止指令重排序,相当于告诉编译器,不要进行代码优化,让它按照本来的顺序执行

此外volatile还有一个作用:保证内存可见性,就是每一时刻线程读取到该变量的值都是内存中最新的那个值(线程每次操作该变量都需要先读取该变量)

 最终代码展示:

class SinngletonLazy{//在第一次调用时创建对象(懒汉模式)//只声明一个类型不创建实际对象private static volatile SinngletonLazy instance = null;public static SinngletonLazy getInstance(){//如果是第一次调用,就加锁创建对象,否则直接返回对象if(instance == null) {//判断是否需要加锁synchronized (SinngletonLazy.class){if (instance == null) {//判断是否需要new对象instance = new SinngletonLazy();}}}return instance;}//私有构造方法private SinngletonLazy(){}
}

 总结

饿汉模式:在类加载时创建对象,通过方法直接返回该对象,不会出现并发安全问题

懒汉模式:在第一次需要对象是才会创建对象,但会有并发问题,建议使用Double Check(双重检验) + Lock(加锁) 可以很好的解决问题

为了在多线程环境下防止,疑问指令重排序而导致代码出现问题,要使用volatile修饰对象

以上就是博主对线程知识的分享,在之后的博客中会陆续分享有关线程的其他知识,如果有不懂的或者有其他见解的欢迎在下方评论或者私信博主,也希望多多支持博主之后和博客!!🥰🥰

下一篇博客博主将分享有关阻塞队列等知识,还希望多多支持一下!!!😊

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

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

相关文章

升级python版本

参考 https://blog.51cto.com/u_15579956/10397535 python3 main.py

聚焦保险行业客户经营现状,概述神策数据 CJO 解决方案

触点红利时代,企业的经营需求从「深度的用户行为分析」转变为「个性化、全渠道一致的客户体验」。客户旅程编排(Customer Journey Orchestration,简称 CJO)从体验出发,关注客户需求、感受和满意度,能够帮助…

HarmonyOS Next系列之地图组件(Map Kit)使用(九)

系列文章目录 HarmonyOS Next 系列之省市区弹窗选择器实现(一) HarmonyOS Next 系列之验证码输入组件实现(二) HarmonyOS Next 系列之底部标签栏TabBar实现(三) HarmonyOS Next 系列之HTTP请求封装和Token…

「运费速查神器」精明买家必备!一键查询1688供应商发货费用

对于从事跨境买家还是国内电商买家,在选品时,需要全面考虑商品成本,发货费用是供应链成本的重要组成部分。 原来如果我们在1688选品看供应商发货运费,需要一个个单独点击到商品的详情页去查看,再选择具体的收货地、再…

Elastic:监控不同于可观察性的 3 个原因

作者:来自 Elastic Observability Team 监控和可观察性经常互换使用,但它们并不完全相同。监控是可观察性的重要组成部分,但可观察性远远超出了传统监控实践的范围。 关键区别:监控从各个组件收集数据 —— 何时和什么&#xff0…

微信小程序-CANVAS写入图片素材、文字等数据生成图片

微信小程序中,CANVAS写入图片素材、文字等数据生成图片,最终可将生成的 base64 格式图片保存至相册操作 Tips: 1、canvas 标签默认宽度 300px、高度 150px canvas 生成图片时,写入图片素材、文字等数据前,需要根据实…

叶再豪降龙精英课程总结

文章目录 1.思维认知1.1 稻盛和夫成功公式1.2 龙头主升模式1.3 龙头主升-两种路径1.4 股市新手的炒股思路1.5 龙头案例1.6 降龙心法 2.情绪周期2.1 情绪周期2.1 情绪演绎周期2.2 情绪的四个部分2.2.1 指数的情绪周期2.2.3 热点情绪周期2.2.4 热点情绪演绎周期2.2.5 大热点支线2…

深入了解路由器工作原理:从零开始的简单讲解

简介 在现代网络中,路由器扮演着至关重要的角色。它不仅连接了不同的设备,还确保数据能够准确地传输到目的地。本文将带你深入探讨路由器的工作原理,帮助网络基础小白们理解这一重要设备的基本功能。 路由器的构成 路由器是一种具有多个输入…

纷享AI | AI技术在销售场景的应用与实践

AI高速发展的今天,各行业都经历着深刻变革。但机遇与挑战总相伴相生,各企业负责人事实上也正面临着如何有效利用AI以完成赋能销售业务的难题。 毋庸置疑,跟上技术潮流,通过落实AI在销售场景中的应用进而取得卓越赋能成果必然是行…

Android TabLayout的简单用法

TabLayout 注意这里添加tab,使用binding.tabLayout.newTab()进行创建 private fun initTabs() {val tab binding.tabLayout.newTab()tab.text "模板库"binding.tabLayout.addTab(tab)binding.tabLayout.addOnTabSelectedListener(object : TabLayout.On…

深度学习系列一

激活函数 sigmod 梯度消失问题: sigmoid函数的导数在输入值较大或较小时接近于0。在反向传播过程中,这些小梯度会相乘,导致深层网络的梯度变得非常小。结果是,深层网络的参数几乎不会更新,训练变得非常困难。这就是为…

Passing output of 3DCNN layer to LSTM layer

题意:将3DCNN(三维卷积神经网络)层的输出传递给LSTM(长短期记忆网络)层 问题背景: Whilst trying to learn Recurrent Neural Networks(RNNs) am trying to train an Automatic Lip Reading Model using 3…

2024年上半年主要游戏安全风险,该如何应对?

随着游戏行业的蓬勃发展,安全问题也日益成为行业关注的焦点。面对 2024 年上半 年的游戏安全风险挑战,游戏行业需要不断加强技术能力,完善安全策略,与各方共 同努力,打造一个更加安全、公平的游戏环境。 游戏安全解…

前端程序员会演化出类TA岗位吗?

前端开发领域确实在不断演化,随着技术的进步和行业的需求变化,前端程序员的角色和职责也在拓展,这自然催生了一系列相关的专业岗位。以下是一些从前端开发领域分化出来的专业角色,我们可以称之为“类TA”(Technical Ad…

BGP之选路MED

原理概述 当一台BGP路由器中存在多条去往同一目标网络的BGP路由时,BGP协议会对这些BGP路由的属性进行比较,以确定去往该目标网络的最优BGP路由。BGP路由属性的比较顺序为Preferred Value属性、Local Preference属性、路由生成方式、AS_Path属性、Origin属…

学习记录——day18 数据结构 树

树的存储 1、顺序存储 对于普通的二叉树,不适合存储普通的二叉树顶序存储,一般用于存储完全二叉树而言,如果使用顺序存储,会浪费大量的存储空间,因为需要给没有节点的位置留出空间,以便于后期的插入。 所以…

20分钟上手新版Skywalking 9.x APM监控系统

Skywalking https://skywalking.apache.org/ Skywalking是专为微服务、云原生和基于容器的(Kubernetes)架构设计的分布式系统性能监控工具。 Skywalking关键特性 ● 分布式跟踪 ○ 端到端分布式跟踪。服务拓扑分析、以服务为中心的可观察性和API仪表板。…

兼容浏览器,切换PC端显示PC端,切换H5端显示H5端

兼容浏览器,切换PC端显示PC端,切换H5端显示H5端 Uniapp vue3 Uview 项目 Vue3 Vite Ts ElementPlus PC端 (在浏览器PC端,切换H5端兼容显示H5端页面) 浏览器H5端 (在浏览器H5端,切换PC端兼容显示PC端…

CSS实现的扫光效果组件

theme: lilsnake 图片和内容如有侵权,及时与我联系~ 详细内容与注释: CSS实现的扫光效果组件 代码 技术栈与框架 Vue3 CSS 扫光效果的原理 扫光效果的原理就是从左到右无限循环的一个位移动画 实现方式 适配文字扫光效果的css .shark-box { …

Stable Diffusion基本原理通俗讲解

Stable Diffusion是一种基于深度学习的图像生成技术,它属于生成对抗网络(GANs)的一种。简单来说,Stable Diffusion通过训练一个生成器(Generator)和一个判别器(Discriminator)&#…