单例模式以及常见的两种实现模式
单例模式是校招中最常考的设计模式之一.
设计模式其实就是类似于“规章制度”,按照这个套路来进行操作。
单例模式能保证某个类在程序中只存在唯一 一份实例。而不会创建出多个实例,如果创建出了多个实例,就会编译报错。而不会创建出多个实例,如果创建出了多个实例,就会编译报错。不使用单例模式也可以做到,就像跟别人借钱说我一定会还一样,但是模式就相当于打了欠条,一定得做到的。这一点在很多场景上都需要. 比如 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…...
【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...
免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...
jdbc查询mysql数据库时,出现id顺序错误的情况
我在repository中的查询语句如下所示,即传入一个List<intager>的数据,返回这些id的问题列表。但是由于数据库查询时ID列表的顺序与预期不一致,会导致返回的id是从小到大排列的,但我不希望这样。 Query("SELECT NEW com…...
32单片机——基本定时器
STM32F103有众多的定时器,其中包括2个基本定时器(TIM6和TIM7)、4个通用定时器(TIM2~TIM5)、2个高级控制定时器(TIM1和TIM8),这些定时器彼此完全独立,不共享任何资源 1、定…...
