双重检验锁方式实现单例模式

单例模式(Singleton Pattern)是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。

单例模式有两种类型:

        1,饿汉式:就是说我很饿看到啥都吃,也就是在类加载的时候就创建给对象。

        2,懒汉式:不是很饿,当真正用到该对象的时候才进行创建。

单例模式注意

        1,单例类只能有一个实例

        2,单例类必须自己创建自己的唯一实例(对象不能new出来)

        3,单例类必须给所有其他对象提供自己创建好的实例

1,对应Java代码实现-饿汉式

//饿汉单例
public class HungrySingle {
    //构造器私有
    private HungrySingle(){
        
    }
    //创建自己的唯一实例
    private static final HungrySingle HUNGRY_SINGLE=new HungrySingle();
    //对外提供自己的这唯一实例
    private static HungrySingle getInstance(){
        return HUNGRY_SINGLE;
    }
}

2,对应Java代码实现-懒汉式

//懒汉单例
public class LazySingle {
    //构造器私有
    private LazySingle(){

    }
    private static LazySingle LAZY_SINGLE;
    private static LazySingle getInstance(){
        //当前实例没有被创建的时候才行创建
        if(LAZY_SINGLE==null){
           LAZY_SINGLE=new LazySingle();
        }
        return LAZY_SINGLE;
    }
}

3,双重检验锁方式实现单例模式   

上述的懒汉单例实现是不完美的,因为我们创建实例的时候需要先判断是否为空,单如果实在多线程环境下,同时判断这个实例对象为空于是就创建了不同的对象,测试如下:

package com.qmlx.springbootinit.Pattern;
//懒汉单例
public class LazySingle {
    //构造器私有
    private LazySingle(){
        System.out.println(Thread.currentThread().getName()+"线程创建了对象");
    }
    private static LazySingle LAZY_SINGLE;
    private static LazySingle getInstance(){
        //当前实例没有被创建的时候才行创建
        if(LAZY_SINGLE==null){
           LAZY_SINGLE=new LazySingle();
        }
        return LAZY_SINGLE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazySingle instance = LazySingle.getInstance();
                System.out.println(instance);
            }).start();
        }
    }
}

代码执行结果如下:

 根据输出以及当前创建的对象,我们可以看到他创建了不同的对象,所以,为了解决这个方法,我们在创建对象的时候使用 synchronized关键字包裹  这样我们就能保证创建对象的操作式互斥的,从而保证对象的单例,这就是常说的双重检验锁方式实现单例模式

代码如下:

//懒汉单例
public class LazySingle {
    //构造器私有
    private LazySingle(){
        System.out.println(Thread.currentThread().getName()+"线程创建了对象");
    }
    private static LazySingle LAZY_SINGLE;
    private static LazySingle getInstance(){
        //当前实例没有被创建的时候才行创建
        if(LAZY_SINGLE==null){
            synchronized (LazySingle.class){
                if(LAZY_SINGLE==null){
                    LAZY_SINGLE=new LazySingle();
                }
            }
        }
        return LAZY_SINGLE;
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazySingle instance = LazySingle.getInstance();
                System.out.println(instance);
            }).start();
        }
    }

}

但是他依旧存在问题,继续往下看。

LAZY_SINGLE=new LazySingle();

这段代码并不是原子性的,因为这段代码其实是分为三部分来执行的:

1,为LAZY_SINGLE分配内存空间

2,初始化LAZY_SINGLE,也就是执行构造方法去初始化这个对象

3,将LAZY_SINGLE指向分配的内存地址

正常步骤是1->2->3,但是JVM具有指令重排序的特性,在单线程下是不会存在问题的,但是在多线程下,会导致一个线程拿到还没有进行初始化的实例,例如线程1执行了1->3,然后线程二获取这个实例,发现不为空,拿到但是结果是没有初始化的。

4,如何防止指令重排序呢?

我们可以使用volatile关键字,他有两个关键作用:

1,保证线程间共享变量的可见性(防止了JIT对共享变量的优化),例如

你写的代码------------------>JIT优化之后的代码

 

所以,我们对变量添加volatile关键字就是告诉JIT编译器,我这个变量你不要优化。

2,防止指令重排序(JVM对程序执行中的优化)

所以我们对当前变量添加volatile关键字,当对这个变量进行读写操作的时候会通过擦汗如特定的内存屏障的方式来禁止指令重排序

具体如下(我的笔记,不知道可不可以帮助理解)

最终代码如下:

//懒汉单例
public class LazySingle {
    //构造器私有
    private LazySingle(){
        System.out.println(Thread.currentThread().getName()+"线程创建了对象");
    }
    private static volatile LazySingle LAZY_SINGLE;
    private static LazySingle getInstance(){
        //当前实例没有被创建的时候才行创建
        if(LAZY_SINGLE==null){
            synchronized (LazySingle.class){
                if(LAZY_SINGLE==null){
                    LAZY_SINGLE=new LazySingle();
                }
            }
        }
        return LAZY_SINGLE;
    }

}

 其实这种实现方式也不是完美的,因为Java中那可是存在反射的,他就可以破坏对象的单例!

具体如何???

等我深刻理解之后,在谈!!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

电源小白入门学习7——USB充电、供电、电源路径管理

电源小白入门学习7——USB充电、供电、电源路径管理 USB充电系统需要考虑的因素开关充电和线性充电充电路径管理输入限流路径管理&#xff08;动态功率管理&#xff09;理想二极管帮助提高电池利用率输入过充抑制 上期我们介绍了锂离子电池的电池特性&#xff0c;及充电电路设计…

OpenNJet评测,探寻云原生之美

在信息时代的大海上&#xff0c;云原生应用引擎如一艘航行于波涛之间的帆船&#xff0c;承载着创新的梦想和数字化的未来。本文将带领您登上这艘船&#xff0c;聚焦其中之一的OpenNJet&#xff0c;一同探寻其中的奥秘和精妙&#xff0c;领略其独特之美。 OpenNJet 内容浅析 O…

【JavaScript】数据类型转换

JavaScript 中的数据类型转换主要包括两种&#xff1a;隐式类型转换&#xff08;Implicit Type Conversion&#xff09;和显式类型转换&#xff08;Explicit Type Conversion&#xff09;。 1. 隐式类型转换&#xff08;自动转换&#xff09;&#xff1a; js 是动态语言&…

代码随想录第51天 | 309.最佳买卖股票时机含冷冻期

309.最佳买卖股票时机含冷冻期 309. 买卖股票的最佳时机含冷冻期 - 力扣&#xff08;LeetCode&#xff09; 代码随想录 (programmercarl.com) 动态规划来决定最佳时机&#xff0c;这次有冷冻期&#xff01;| LeetCode&#xff1a;309.买卖股票的最佳时机含冷冻期_哔哩哔哩_bi…

ncnn 算子操作描述

ncnn 算子操作描述&#xff0c;具体查询见 ncnn/docs/developer-guide/operators.md at master Tencent/ncnn GitHub 都是从上述地方copy过来的&#xff0c;做备份。 具体如下&#xff1a; 1.AbsVal: 计算输入张量中的每个元素的绝对值。 y abs(x)one_blob_only 只支持…

Go 语言(四)【常用包使用】

1、命令行参数包 flag flag 包就是一个用来解析命令行参数的工具。 1.1、os.Args import ("fmt""os" )func main() {if len(os.Args) > 0 {for index, arg : range os.Args {fmt.Printf("args[%d]%v\n", index, arg)}} } 运行结果&#…

【Docker】docker部署lnmp和搭建wordpress网站

环境准备 docker&#xff1a;192.168.67.30 虚拟机&#xff1a;4核4G systemctl stop firewalld systemctl disable firewalld setenforce 0 安装docker #安装依赖包 yum -y install yum-utils device-mapper-persistent-data lvm2 #设置阿里云镜像 yum-config-manager --add…

MTEB - Embedding 模型排行榜

文章目录 关于 MTEBMTEB 任务和数据集概览使用 MTEB Pythont 库Installation使用 关于 MTEB MTEB : Massive Text Embedding Benchmark github : https://github.com/embeddings-benchmark/mtebhuggingface : https://huggingface.co/spaces/mteb/leaderboardpaper : https:/…

全国31省对外开放程度、经济发展水平、ZF干预程度指标数据(2000-2022年)

01、数据介绍 自2000年至2022年&#xff0c;中国的对外开放程度不断深化、经济发展水平不断提高、ZF不断探索并调整自身在经济运行中的角色和定位&#xff0c;以更好地适应国内外环境的变化&#xff0c;也取得了举世瞩目的成就。这一期间&#xff0c;中国积极融入全球经济体系…

书籍推荐|经典书籍ic书籍REUSE METHODOLOGY MANUALFOR等和verilog网站推荐(附下载)

大家好&#xff0c;今天是51过后的第一个工作日&#xff0c;想必大家都还没有完全从节假日的吃喝玩乐模式转变为勤勤恳恳的打工人模式&#xff0c;当然也包括我&#xff0c;因此这次更新主要是分享几篇书籍和verilog相关的学习网站~ 首先是一本数字电路相关的基础书籍&#xf…

JavaScript 中的 Class 类

&#x1f525; 引言 在ECMAScript 2015&#xff08;ES6&#xff09;中&#xff0c;class 关键字被引入&#xff0c;为JavaScript带来了一种更接近传统面向对象语言的语法糖。类是创建对象的模板&#xff0c;它们封装了数据&#xff08;属性&#xff09;和行为&#xff08;方法&…

【SpringMVC 】什么是SpringMVC(一)?如何创建一个简单的springMvc应用?

文章目录 SpringMVC第一章1、什么是SpringMVC2、创建第一个SpringMVC的应用1-3步第4步第5步第6步7-8步3、基本语法1、进入控制器类的方式方式1:方式2:方式3:方式4:方式5:2、在控制器类中取值的方式方式1:方式2:方式3:方式4:方式5:方式6:超链接方式7:日期方式8:aja…

第一天学习(GPT)

1.图片和语义是如何映射的&#xff1f; **Dalle2&#xff1a;**首先会对图片和语义进行预训练&#xff0c;将二者向量存储起来&#xff0c;然后将语义的vector向量转成图片的向量&#xff0c;然后基于这个图片往回反向映射&#xff08;Diffusion&#xff09;——>根据这段描…

云原生周刊:Terraform 1.8 发布 | 2024.5.6

开源项目推荐 xlskubectl 用于控制 Kubernetes 集群的电子表格。xlskubectl 将 Google Spreadsheet 与 Kubernetes 集成。你可以通过用于跟踪费用的同一电子表格来管理集群。 git-sync git-sync 是一个简单的命令&#xff0c;它将 git 存储库拉入本地目录&#xff0c;等待一…

深度神经网络中的不确定性研究综述

A.单一确定性方法 对于确定性神经网络&#xff0c;参数是确定的&#xff0c;每次向前传递的重复都会产生相同的结果。对于不确定性量化的单一确定性网络方法&#xff0c;我们总结了在确定性网络中基于单一正向传递计算预测y *的不确定性的所有方法。在文献中&#xff0c;可以找…

如何取消xhr / fetch / axios请求

如何取消xhr请求 setTimeout(() > { xhr.abort() }, 1000)如何取消fetch请求 fetch()请求发送以后&#xff0c;如果中途想要取消&#xff0c;需要使用AbortController对象。 let controller new AbortController(); let signal controller.signal;fetch(url, {signal:…

[激光原理与应用-92]:振镜的光路图原理

目录 一、振镜的光路 二、振镜的工作原理 2.1 概述 2.2 焊接头 2.3 准直聚焦头-直吹头 2.4 准直聚焦头分类——按应用分 2.4.1 准直聚焦头分类——功能分类 2.4.2 准直聚焦头镜片 2.4.3 振镜焊接头 2.4.4 振镜分类&#xff1a; 2.4.5 动态聚焦系统演示&#xff08;素…

vivado Virtex 和 Kintex UltraScale+ 比特流设置

下表所示 Virtex 和 Kintex UltraScale 器件的器件配置设置可搭配 set_property <Setting> <Value> [current_design] Vivado 工具 Tcl 命令一起使用。

Python使用割圆法求π值

三国时期刘徽提出的割圆法有多牛掰&#xff0c;看这个&#xff1a;刘徽割圆术到底做了什么&#xff1f; - 知乎 用Python实现的该算法代码如下&#xff1a; #!/usr/bin/env python """使用割圆法计算π值Usage::$ python calc_circle_pi.py 20 # 参数20是迭代…

ArthasGC日志GCeasy详解

Arthas详解 Arthas是阿里巴巴在2018年9月开源的Java诊断工具,支持JDK6,采用命令行交互模式,可以方便定位和诊断线上程序运行问题.Arthas官方文档十分详细.详见:官方文档 Arthas使用场景 Arthas使用 # github下载arthas wget https://alibaba.github.io/arthas/arthas-boot.j…