单例模式以及常见的两种实现模式
单例模式是校招中最常考的设计模式之一.
设计模式其实就是类似于“规章制度”,按照这个套路来进行操作。
单例模式能保证某个类在程序中只存在唯一 一份实例。而不会创建出多个实例,如果创建出了多个实例,就会编译报错。而不会创建出多个实例,如果创建出了多个实例,就会编译报错。不使用单例模式也可以做到,就像跟别人借钱说我一定会还一样,但是模式就相当于打了欠条,一定得做到的。这一点在很多场景上都需要. 比如 JDBC 中的 DataSource 实例就只需要一个。
单例模式具体的实现方式, 分成 "饿汉" 和 "懒汉" 两种.
饿汉模式
类加载的同时, 创建实例(给人一种很急的感觉)
// 饿汉模式的 单例模式 实现.
// 此处保证 Singleton 这个类只能创建出一个实例.
class Singleton {// 在此处, 先把这个实例给创建出来了.private static Singleton instance = new Singleton();// 如果需要使用这个唯一实例, 统一通过 Singleton.getInstance() 方式来获取.public static Singleton getInstance() {return instance;}// 为了避免 Singleton 类不小心被复制出多份来.// 把构造方法设为 private. 在类外面, 就无法通过 new 的方式来创建这个 Singleton 实例了!!private Singleton() {}
}public class ThreadDemo19 {public static void main(String[] args) {Singleton s = Singleton.getInstance();Singleton s2 = Singleton.getInstance();// Singleton s3 = new Singleton();System.out.println(s == s2);}
}
运行一个 Java 程序,会先让 Java 进程找到并读取对应的 .class 文件,就会读取文件内容并解析,构造成类对象......这一系列的过程操作就叫做 类加载。
因为 static 修饰的变量落入到了类对象里面,又因为类对象是在类加载阶段内创建出来的唯一一个实例,同时构造方法是 private 修饰的,因此就只有这一个实例的成员了。
懒汉模式
类加载的时候不创建实例,第一次使用的时候才创建实例,如果不用就不创建了(效率更高了)
class SingletonLazy {private static SingletonLazy instance = null;public static SingletonLazy getInstance() {if (instance == null) {instance = new SingletonLazy();}return instance;}private SingletonLazy() {}
}public class ThreadDemo20 {public static void main(String[] args) {SingletonLazy s = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();System.out.println(s == s2);}
}
上述的这两种模式,饿汉模式只涉及到“读操作”,懒汉模式既涉及到“读操作”也涉及到“写操作”,因此这个在多线程环境下会有线程安全问题。
因此加上 synchronized 可以改善这里的线程安全问题。
public static SingletonLazy getInstance() {// 这一层 if 是因为只要对象被 new 了一次就不用再加锁产生更多开销了if (instance == null) {synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}
也就是说,如果对象还有没有创建那么就要进行加锁,如果对象已经创建过了就不用加锁了,因为最后都是“读操作”,此时不加锁也没事。
懒汉模式-多线程版(改进)
假设有很多线程都去进行 getInstance,这个时候就会出现内存可见性问题(编译器优化:只有第一次真正读了内存,后续都是读寄存器 / cache)
同时还会有指令重排序问题:
instance = new Singleton();可以拆分成三个步骤
1.申请内存空间
2.调用构造方法,把这个内存空间初始化成一个合理的对象
3.把内存空间的地址赋值给 instance 引用
正常情况下是1 2 3 顺序来执行的,但是编译器会为了提高效率从而调整顺序,可能就变成1 3 2,如果是单线程就没有区别。但在多线程环境下,假设 t1 是按照 1 3 2 执行的,当 t1 执行到 1 3 之后,准备执行 2 的时候,t2 跑过来执行了。此时在 t2 的角度 instance 就非空了,就会直接返回instance 了,但由于 t1 的 2 指令还没执行完,t2 拿到的是一个非法的对象(还没构造完成的不完整的对象),这时候如果尝试使用引用中的属性就会出现错误。例如 instance 里有个成员 num,构造方法是要初始化成100的,但是由于上述问题就导致构造方法还没执行,此时访问 num 是 0。
因此加上 volatile 可以解决内存可见性问题和禁止指令重排序。
class SingletonLazy {private volatile static SingletonLazy instance = null;public static SingletonLazy getInstance() {if (instance == null) {synchronized (SingletonLazy.class) { //加锁不是这个线程就一直赖着不走,而是切换调度正常,但是其他线程尝试加锁的时候就会阻塞。if (instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy() {}
}public class ThreadDemo20 {public static void main(String[] args) {SingletonLazy s = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();System.out.println(s == s2);}
}
理解双重 if 判定 / volatile:
加锁 / 解锁是一件开销比较高的事情. 而懒汉模式的线程不安全只是发生在首次创建实例的时候.
因此后续使用的时候, 不必再进行加锁了.
外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了.
同时为了避免 "内存可见性" 导致读取的 instance 出现偏差, 于是补充上 volatile .
当多线程首次调用 getInstance, 大家可能都发现 instance 为 null, 于是又继续往下执行来竞争锁,
其中竞争成功的线程, 再完成创建实例的操作.
当这个实例创建完了之后, 其他竞争到锁的线程就被里层 if 挡住了. 也就不会继续创建其他实例.
1) 有三个线程, 开始执行 getInstance , 通过外层的 if (instance == null) 知道了实例还没
有创建的消息. 于是开始竞争同一把锁.
2) 其中线程1 率先获取到锁, 此时线程1 通过里层的 if (instance == null) 进一步确认实例是
否已经创建. 如果没创建, 就把这个实例创建出来.
3) 当线程1 释放锁之后, 线程2 和 线程3 也拿到锁, 也通过里层的 if (instance == null) 来
确认实例是否已经创建, 发现实例已经创建出来了, 就不再创建了.
4) 后续的线程, 不必加锁, 直接就通过外层 if (instance == null) 就知道实例已经创建了, 从
而不再尝试获取锁了. 降低了开销.
相关文章:

单例模式以及常见的两种实现模式
单例模式是校招中最常考的设计模式之一. 设计模式其实就是类似于“规章制度”,按照这个套路来进行操作。 单例模式能保证某个类在程序中只存在唯一 一份实例。而不会创建出多个实例,如果创建出了多个实例,就会编译报错。而不会创建出多个实…...
Java hashCode() 和 equals()的若干问题解答
Java hashCode() 和 equals()的若干问题解答 本章的内容主要解决下面几个问题: 1 equals() 的作用是什么? 2 equals() 与 的区别是什么? 3 hashCode() 的作用是什么? 4 hashCode() 和 equals() 之间有什么联系? …...
高级IO——React服务器简单实现
3.4Reactor服务器实现 1.connect封装 每一个连接都要有一个文件描述符和输入输出缓冲区,还有读、写、异常处理的回调方法; 还包括指向服务器的回指指针; class connection; class tcpserver;using func_t std::function<void(s…...

Qt使用插件QPluginLoader 机制开发
简介: 插件(Plug-in,又称addin、add-in、addon或add-on,又译外挂)是一种遵循一定规范的应用程序接口编写出来的程序。 Qt 提供了2种APIs来创建插件: 一种高级API,用于为Qt本身编写插件:自定义数据库驱动程序,图像格…...

双子座 Gemini1.5和谷歌的本质
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...

二百三十、MySQL——MySQL表的索引
1 目的 梳理一下目前MySQL维度表的索引情况,当然网上也有其他博客专门讲MySQL索引的,我这边只是梳理一下目前的索引状况而已 2单列索引 2.1 索引截图 2.2 建表语句 3 联合索引 3.1 索引截图 3.2 建表语句 4 参考的优秀博客 http://t.csdnimg.cn/ZF7…...

并发编程之ThreadLocal使用及原理
ThreadLocal主要是为了解决线程安全性问题的 非线程安全举例 public class ThreadLocalDemo {// 非线程安全的private static final SimpleDateFormat sdf new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static Date parse(String strDate) throws ParseExc…...

软件测试 测试开发丨Pytest结合数据驱动-yaml,熬夜整理蚂蚁金服软件测试高级笔试题
编程语言 languages: PHPJavaPython book: Python入门: # 书籍名称 price: 55.5 author: Lily available: True repertory: 20 date: 2018-02-17 Java入门: price: 60 author: Lily available: False repertory: Null date: 2018-05-11 yaml 文件使用 查看 yaml 文件 pych…...

软考数据库---2.SQL语言
主要记忆:表、索引、视图操作语句;数据操作;通配符、转义符;授权;存储过程;触发器 这部分等等整理一下: “”" 1、 数据定义语言。 SQL DDL提供定义关系模式和视图、 删除关系和视图、 修改关系模式的…...

基于顺序表实现通讯录
上篇我们讲了顺序表是什么,和如何实现顺序表。这篇文章我们将基于顺序表来实现通讯录。 文章目录 前言一、基于顺序表是如何实现的二、通讯录的头文件和实现文件三、通讯录的实现3.1 定义通讯录结构3.2 初始化通讯录3.3 销毁通讯录3.4 通讯录添加数据3.5 查找联系人…...

咸鱼之王_手游_开服搭建架设_内购修复无bug运营版
视频演示 咸鱼之王_手游_开服 游戏管理后台界面 源码获取在文章末尾 源码获取在文章末尾 源码获取在文章末尾 或者直接下面 https://githubs.xyz/y28.html 1.安装宝塔 yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh &…...

【JSON2WEB】14 基于Amis的CRUD开发30分钟速成
【JSON2WEB】系列目录 【JSON2WEB】01 WEB管理信息系统架构设计 【JSON2WEB】02 JSON2WEB初步UI设计 【JSON2WEB】03 go的模板包html/template的使用 【JSON2WEB】04 amis低代码前端框架介绍 【JSON2WEB】05 前端开发三件套 HTML CSS JavaScript 速成 【JSON2WEB】06 JSO…...

Java入门教程||Java 变量
Java 变量 Java教程 - Java变量 变量由标识符,类型和可选的初始化程序定义。变量还具有范围(可见性/生存期)。 Java变量类型 在Java中,必须先声明所有变量,然后才能使用它们。变量声明的基本形式如下所示࿱…...

基于Java的校园快递一站式服务系统 (源码+文档+包运行)
一.系统概述 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本校园快递一站式服务系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞…...

通讯录的实现(顺序表版本)
我们知道通讯录是基于顺序表的前提下,要写好通讯录我们就要深入了解好顺序表。我们先来看看什么是顺序表。(注意今天代码量有点多,坚持一下)。冲啊!兄弟们! 顺序表的简单理解 对于顺序表,我们首…...

利用Sentinel解决雪崩问题(一)流量控制
1、解决雪崩问题的常见方式有四种: 超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待;舱壁模式:限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离;熔断降级:由断路器统计业务…...
二叉树总结
递归返回值 1、如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。 2、如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 3、如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,…...

接口优化技巧
一、背景 针对老项目,去年做了许多降本增效的事情,其中发现最多的就是接口耗时过长的问题,就集中搞了一次接口性能优化。本文将给小伙伴们分享一下接口优化的通用方案 二、接口优化方案总结 1.批处理 批量思想:批量操作数据库&a…...

【工具】NPS 内网穿透搭建
背景 在日常开发中经常会涉及到使用公网某个端口进行开发调试的情况,但我们日常开发的机器IP是非公网IP,所以需要使用内网穿透的手段,使我们的服务在公网上能被访问到。 常用的内网穿透工具分两大类,一类是付费/免费服务…...

【数学】主成分分析(PCA)的详细深度推导过程
本文基于Deep Learning (2017, MIT),推导过程补全了所涉及的知识及书中推导过程中跳跃和省略的部分。 blog 1 概述 现代数据集,如网络索引、高分辨率图像、气象学、实验测量等,通常包含高维特征,高纬度的数据可能不清晰、冗余&am…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

算法:模拟
1.替换所有的问号 1576. 替换所有的问号 - 力扣(LeetCode) 遍历字符串:通过外层循环逐一检查每个字符。遇到 ? 时处理: 内层循环遍历小写字母(a 到 z)。对每个字母检查是否满足: 与…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...