单例模式:懒汉模式

article/2025/9/22 2:31:51

所谓“懒汉式”与“饿汉式”的区别,是在与建立单例对象的时间的不同。

  •  “懒汉式”是在你真正用到的时候才去建这个单例对象
  • “饿汉式是在类创建的同时就已经创建好一个静态的对象,不管你用的用不上,一开始就建立这个单例对象

代码实现:

  • 懒汉模式:
public class Singleton2 {private volatile static Singleton2 singleton; // 5private Singleton2() {System.out.println("懒汉式单例初始化!");}public static Singleton2 getInstance () {if(singleton ==null) { // 1synchronized(Singleton2.class) { // 2if(singleton == null) { // 3singleton = new Singleton2(); //4}}}return singleton;}}

 代码1 处的判空是为了减少同步方法的使用,提高效率
代码2,3 处的加锁和判空是为了防止多线程下重复实例化单例。
代码5 处的volatile是为了防止多线程下代码4 的指令重排序 

Volatile关键字的作用: 禁止进行指令的重排序

模拟没有volatile出错场景:

以第一段代码为例,我们可以模拟一下两条线程运行的情况:

  1. 线程A 访问了getInstance 方法,发现两次 singleton== null 检查都是 true,于是线程A 开始初始化 Singleton2 此处时间点设为 x
  2. 在时间点 x 线程B 访问了 getInstance 方法,此时它发现 singleton已经是非 null 了,于是高高兴兴地返回了 Singleton2 实例,并且外部调用开始使用 Singleton2 的实例,此处时间点设为 y

问题在于:时间点 y 的 singletonInstance 实例真的初始化好了吗

由于第一段代码里忽略了 SingletonInstance 类的其他实例域,我们可以假设充实一下SingletonInstance 类

public class SingletonPattern {private int num;private boolean really;private SingletonPattern(){this.num = 2020;this.really = true;}private static SingletonPattern singletonInstance;public static SingletonPattern getSingletonInstance() {if (singletonInstance == null) {synchronized (SingletonPattern.class) {if (singletonInstance == null) {singletonInstance = new SingletonPattern();}}}return singletonInstance;}
}

在JVM新建一个对象实例时,首先会赋予这个实例的所有域初始值(比如 int 的初始值是 0, boolean的初始值是 false),然后再调用构造器方法。也就是说,有那么一个时刻(比如就是时间点 y )新建的SingletonPattern实例的 num 值为 0, really 值为 false。

此外,JVM 的指令重排可能导致了这个对象实例先被赋值给了变量,然后才开始初始化,所以时间点 y 变量已经不为 null,而实例尚未初始化结束。

正在 时间点 y,线程B已经开始使用了SingletonPattern实例,这就是一个 bug 了,因为SingletonPattern实例根本还没初始化结束!

类似的,第二段懒汉式代码一样有这个问题。

既然上面那两段代码有问题,那如何修正呢?

很简单,加个关键字就好了 —— volatile

静态域版本

public class SingletonPattern {// ... 忽略其他域定义和方法private static volatile SingletonPattern singletonInstance;public static SingletonPattern getSingletonInstance() {if (singletonInstance == null) {synchronized (SingletonPattern.class) {if (singletonInstance == null) {singletonInstance = new SingletonPattern();}}}return singletonInstance;}
}

实例域版本

public class MyClass {private volatile MyField field;public MyField initField() {if (field == null) {synchronized (MyClass.class) {if (field == null) {field = new MyField();}}}return field;}
}

为什么 volatile 可以解决以上问题?

依然以静态域版本为例,当 volatile 修饰了singletonInstance变量之后,就会禁止JVM对这个变量相关操作做指令重排 —— 即在调用 new SingletonPattern() 初始化SingletonPattern实例时(写操作),JVM 会保证SingletonPattern实例完全初始化结束后才允许其他线程访问(读操作)这个实例。

有更好的实现吗?

对于静态域的延迟初始化, lazy initialization holder class 模式不仅性能更好,代码可读性也更高

public class SingletonPattern {// ... 忽略其他域定义和方法private static class SingletonPatternHolder{static final SingletonPattern instance = new SingletonPattern();}public static SingletonPattern getSingletonInstance() {return SingletonPatternHolder.instance;}
}

不算括号,4行代码就搞定

当getSingletonInstance()被调用时,SingletonPatternHolder类才第一次被JVM加载,且JVM会以线程安全的方式初始化好SingletonPatternHolder类的静态域——也就是 SingletonPatternHolder.instance

所以 getSingletonInstance() 是线程安全的。

 

而实例域的延迟初始化的这种双重检查模式,除了加 volatile 关键字也可以尝试把这个域改造成不可变的对象—— 原理是读和写一个不可变对象都是原子性的,也就不可能读到一个初始化未完成的不可变对象。

  • 饿汉式

 

public class Singleton1 {private final static Singleton1 singleton = new Singleton();private Singleton1() {System.out.println("饿汉式单例初始化!");}public static Singleton1 getSingleton () {return singleton;}
}

 

在类静态变量里直接new一个单例

 

 

懒汉模式构建ESRestAPIUtils

import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicHeader;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.client.*;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class ESRestAPIUtils {/*** 声明一个对象的引用*/private static volatile RestClient restClient;private static volatile RequestOptions COMMON_OPTIONS;/*** 私有构造方法*/private ESRestAPIUtils(){}/*** @DESC: 初始化ES客户端*/private static RestClient initClient() {RestClientBuilder builder = RestClient.builder(assembleESHost().toArray(new HttpHost[assembleESHost().size()]));Header[] header = new Header[]{new BasicHeader("header", "value")};builder.setDefaultHeaders(header);return builder.build();}/*** @DESC: 初始化RequestOptions*/private static RequestOptions initRequestOptions(){RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();builder.setHttpAsyncResponseConsumerFactory(new HttpAsyncResponseConsumerFactory.HeapBufferedResponseConsumerFactory(800 * 1024 * 1024));return builder.build();}/**判断一下如果对象为null,在创建一个对象* 多线程的中使用的时候,线程优化,加一个锁* @DESC: 获取RequestOptions 的COMMON_OPTIONS对象,懒汉模式*/public static RequestOptions getRequestOptions() {if (null == COMMON_OPTIONS) {synchronized (ESRestAPIUtils.class) {if (null == COMMON_OPTIONS) {try {COMMON_OPTIONS = initRequestOptions();//esClusterClient.settings();} catch (Exception e) {log.error("RequestOptions创建失败...." + COMMON_OPTIONS, e);}}}}return COMMON_OPTIONS;}/**判断一下如果对象为null,在创建一个对象* 多线程的中使用的时候,线程优化,加一个锁* @DESC: 获取RestClientBuilder,懒汉模式*/public static RestClient getRestClient() {if (null == restClient) {synchronized (ESRestAPIUtils.class) {if (null == restClient) {try {restClient = initClient();//esClusterClient.settings();} catch (Exception e) {log.error("ESClient创建失败...." + restClient, e);}}}}return restClient;}
/*** @return : 返回请求后的json字符串* @DESC: 可接受所有对于ES的查询操作,包括数据写入、删除、一般的条件查询和聚合查询*/public static String query(String queryJson, String method, String endpoint) {String resultJson = "";
//        RestClient restClient = restClient.build();//     .setMaxRetryTimeoutMillis(10 * 60 * 1000)/**设置查询超时时长,默认30秒*/.build();HttpEntity entity = new StringEntity(queryJson, ContentType.APPLICATION_JSON);//说明提供的是一个json格式的queryResponse response;try {Request request = new Request(method, endpoint);request.setEntity(entity);request.addParameter("pretty", "true");RequestOptions options = ESRestAPIUtils.getRequestOptions();request.setOptions(options);RestClient client = ESRestAPIUtils.getRestClient();response = client.performRequest(request);resultJson = EntityUtils.toString(response.getEntity(), "UTF-8");
//            try { //为什么要去掉关闭restclient:懒汉的目的,就是让服务始终保持有一个RestClient,如果我们关闭后,新启的查询又需要去重新new一个对象,这样浪费时间。懒汉的初衷就是空间换时间。
//                client.close();//关闭后,对象并不为空
//                client =null;//设置对象为空
//            } catch (IOException e) {
//                log.error("Rest客户端关闭失败...", e);
//            }return resultJson;} catch (IOException e) {log.error("查询请求失败..." + queryJson, e);return resultJson;}}/*** 通过ConsulUtils获取ES的节点配置,并封装为HttpHost* @DESC: 组装ES的hosts为HttpHost*/private static List<HttpHost> assembleESHost() {ArrayList<HttpHost> esHostList = new ArrayList<>();String[] esAddrArray = ConfigUtils.getSingleConf("es/cluster.nodes").split(",");for (String es : esAddrArray)esHostList.add(new HttpHost(es, Integer.valueOf(ConfigUtils.getSingleConf("es/cluster.http.port"))));return esHostList;}
}

 


http://chatgpt.dhexx.cn/article/fxjdiseX.shtml

相关文章

java 单例模式 之懒汉模式

单例模式&#xff1a;一个类&#xff0c;始终仅仅对外提供自己的一个实例&#xff0c;这样的设计方案&#xff0c;就称单例模式。 懒汉模式&#xff1a; 构造函数私有 声明私有的本类静态实例 定义静态的方法&#xff0c;在方法中创建本类实例&#xff0c;并返回该实例 pu…

单例模式饿汉模式与懒汉模式

目录 1.什么是单例模式 2.为什么需要单例模式&#xff1f; 3.如何实现单例模式 3.1饿汉方式 3.2懒汉模式 1.什么是单例模式 单例模式是一种设计模式&#xff0c;单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例。单例模式的具体实现又分为饿汉模式…

关于Java中单例模式(饿汉模式和懒汉模式)的简析

目录 一.什么是单例模式 二.饿汉模式和懒汉模式 饿汉模式 代码 懒汉模式 代码 关于多线程安全的问题 如何解决懒汉模式多线程安全问题 双if判断 一.什么是单例模式 简单来说,就是我们在程序中通过代码进行限制,在该程序中 只能创建一个对象 二.饿汉模式和懒汉模式 …

java设计模式之单例模式|单例模式之饿汉模式、懒汉模式、枚举方式|最详细的6种懒汉模式详解

目录 一、单例模式 二、饿汉模式和懒汉模式 1、饿汉模式&#xff0c;线程安全 2、懒汉模式 懒汉模式1&#xff0c;线程不安全&#xff08;不常用&#xff09; 懒汉模式2&#xff0c;线程安全&#xff08;不常用&#xff09; 懒汉模式3&#xff0c;线程安全&#xff0c;双…

全志F1C100s主线linux入坑记录 (10)调试串口更改

调试串口更改 百度网站 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 调试串口更改前言uboot 修改一、修改设备树二、修改文件3. 修改内核传递参数 内核修改参考 前言 未完成版本 未完成版本 未完成版本 未完成…

f1c100s 调试问题汇总

问题 usb无法识别 windows显示无法识别的usb设备 解决&#xff1a; 卸载设备&#xff0c;插拔一下&#xff0c;就可以识别了&#xff0c;之后就会自动安装驱动。 umount失败 fuser ./d2 可以显示出当前哪个程序在使用磁盘上的某个文件、挂载点、甚至网络端口&#xff0c;并…

【f1c200s/f1c100s】FT5426触摸屏驱动适配

【f1c200s/f1c100s】FT5426触摸屏驱动适配 前言设备树配置IIC控制器FT5426设备树配置 内核配置结果 前言 嵌入式linux下的触摸屏驱动是基于input子系统的&#xff0c;当触摸发生时&#xff0c;内核上报触摸事件至用户层。我使用的显示屏是正点原子的7寸RGB接口显示屏&#xff…

f1c100s开发笔记

f1c100s开发笔记 全志芯片相关的论坛帖f1c100s移植帖交叉编译器的安装uboot的编译适配配置开始编译uboot编译遇坑 2020-05-20 09:56:15 星期四 全志芯片相关的论坛帖 https://whycan.cn/t_3019.html#p25005 f1c100s移植帖 https://whycan.cn/t_3211.html 交叉编译器的安装 …

全志F1C100S/F1C200S学习笔记(1)——基础简介及资料

文章目录 一、芯片概览二、芯片框图三、芯片规格四、资料:五、仓库:一、芯片概览 二、芯片框图 三、芯片规格 功能描述CPUARM9 CPU architecture16KByte D

f1c100linux系统吗,全志F1C100s怎么样 F1C100s芯片参数介绍

全志F1C100s芯片怎么样&#xff0c;F1C100s处理器好用吗&#xff1f;F1C100s是720P高清多媒体处理器。下面带来F1C100s芯片的具体参数&#xff0c;准备入手搭载F1C100s芯片设备的用户可以参考一下。 F1C100s芯片架构图 F1C100s特性介绍 支持H.264 1920x108030fps 解码 支持MJPE…

全志F1C100S的BROM研究

全志f1c100s是个性价比很高的芯片&#xff0c;但是对一般人不太友好的是它的资料开放的太少了。 网上找不到完整版的用户手册&#xff0c;只能从有限的手册文档和参考代码旁敲侧击&#xff0c;反向猜测。 关于它的BROM网上的手册内容很少。 手册上只有短短3句话&#xff1a; 具…

10、Lctech Pi(F1C200S)驱动电阻屏触摸芯片ns2009(ts2007),buildroot配置tslib(CherryPi,Mangopi,F1C100S)

本次主要参考&#xff1a; https://github.com/mangopi-sbc/buildroot-mangopi-r https://blog.csdn.net/qq_35031421/article/details/113436888 https://blog.csdn.net/dancheqishi23/article/details/116498088 &#xff08;如果方便请给这几位大佬一个关注&#xff09; 开…

F1C100S自制开发板调试过程

疫情&#xff0c;等了好久板子终于到了。 我这里使用的是坑网大佬提供的tiny200开发包&#xff0c;用的芒果派R3配置文件 1&#xff0c;配置其的介质&#xff0c;我板子上用的是nor-spi-flash,所以需要在设备树里面屏蔽掉nand-flash相关的节点&#xff0c;否则启动会有错误。 …

F1C100S(Lichee Nano)触摸屏 (GT9147)

1、前提 Ubuntu 环境版本 (18.04) Linux ubuntu 5.4.0-131-generic #147~18.04.1-Ubuntu SMP Sat Oct 15 13:10:18 UTC 2022 x86_64 x86_64 x86_64 GNU/LinuxARM GCC版本 gcc version 7.2.1 20171011 (Linaro GCC 7.2-2017.11)F1C100S Linux版本 (linux-nano-5.2-tf) 链接 …

全志F1C100s主线linux入坑记录 (5)LVGL8.2移植

LVGL8.2移植 百度网站 文章目录 LVGL8.2移植一、安装VScode二、安装lvgl模拟器二、F1c100s 移植lvgl参考 一、安装VScode 进入VScode官网下载安装包&#xff0c;如果最新版本安装有问题可以安装老版本的 https://code.visualstudio.com/使用命令行安装VScode sudo dpkg -i …

全志F1C100s主线linux入坑记录 (7)GBA模拟器移植

GBA模拟器移植 百度网站 文章目录 GBA模拟器移植一、下载gpsp 源代碼二、gpsp环境配置参考 一、下载gpsp 源代碼 gpsp源码 解压文件 7z x gpsp.7z 二、gpsp环境配置 gpsp需要SDL环境我们先在bulidroot中添加SDL包 make menuconfigTarget packages ---> Graphic libra…

9、Lctech Pi(F1C200S)开启I2C0(CherryPi,Mangopi,F1C100S)

本次主要参考&#xff1a; https://github.com/mangopi-sbc/buildroot-mangopi-r https://wiki.sipeed.com/soft/Lichee/zh/Nano-Doc-Backup/index.html &#xff08;如果方便请给这几位大佬一个关注&#xff09; 注意代码块之间的空行 配置设备树文件 1、打开linux-5.7.1/a…

全志F1C100s主线linux入坑记录 (3)适配其他分辨率的LCD

适配其他分辨率的LCD 百度网站 文章目录 适配其他分辨率的LCD一、修改U-boot屏幕参数二、修改linux内核文件三、测试效果 一、修改U-boot屏幕参数 修改对应屏幕的参数&#xff08;我这里是1024*600的屏幕&#xff09; 修改 -> ARM architecture -> Enable graphical ub…

全志F1C100S从零开发记录(1)

1.刚买到板子&#xff0c;开箱如下图所示&#xff1a; 2.管脚焊接&#xff08;焊接串口&#xff0c;用来看串口打印信息&#xff09;&#xff0c;5v供电&#xff1a; 3.通过usb转ttl接到电脑上&#xff1a; . 4.设置串口波特率115200 &#xff0c;打开串口&#xff0c;上电出…

7、Lctech Pi(F1C200S)开启RNDIS,通过USB与电脑联网(CherryPi,Mangopi,F1C100S)

本次主要参考&#xff1a; 荔枝nano开启RNDIS驱动&#xff0c;无需补丁。 https://github.com/peng-zhihui/Planck-Pi#head23 https://github.com/mangopi-sbc/buildroot-mangopi-r https://www.cnblogs.com/listenscience/p/13758272.html &#xff08;如果方便请给这几位大佬…