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

embedded/2024/10/19 9:36:28/

单例模式(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中那可是存在反射的,他就可以破坏对象的单例!

具体如何???

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


http://www.ppmy.cn/embedded/35441.html

相关文章

若依分离版-前端使用echarts组件

1 npm list:显示已安装的模块 该命令用于列出当前项目的所有依赖关系&#xff0c;包括直接依赖和间接依赖。执行 npm list 时&#xff0c;npm 将从当前目录开始&#xff0c;递归地列出所有已安装的模块及其版本信息 npm list 2 npm outdated:用于检查当前项目中的npm包是否有…

关于 win11 系统下12代/13代英特尔大小核架构 CPU 的 VMware 优化:输入延迟、卡顿,大小核调度

关于 win11 系统下12代/13代英特尔大小核架构 CPU 的 VMware 优化&#xff1a;输入延迟、卡顿&#xff0c;大小核调度 一、前言二、VMware 的优化2.1 键鼠输入延迟问题的解决2.1.1 搜索内核隔离2.1.2 关闭内存完整性并重启2.1.3 搜索启用或关闭windows功能2.1.4 关闭 hyper-v 和…

BJFUOJ-C++程序设计-实验2-类与对象

A 评分程序 答案&#xff1a; #include<iostream> #include<cstring>using namespace std;class Score{ private:string name;//记录学生姓名double s[4];//存储4次成绩&#xff0c;s[0]和s[1]存储2次随堂考试&#xff0c;s[2]存储期中考试&#xff0c;s[3]存储期…

Android 安装过程三 MSG_ON_SESSION_SEALED、MSG_STREAM_VALIDATE_AND_COMMIT的处理

Android 安装过程一 界面跳转 知道&#xff0c;在InstallInstalling Activity中&#xff0c;PackageInstallerSession对象创建之后&#xff0c;接着会打开它&#xff0c;然后将安装文件进行拷贝&#xff0c;拷贝完成之后&#xff0c;会对Session对象确认。   从Session对象确…

数据库优化

一、主从读写分离 主库:主要负责数据的写入。 从库:主要负责数据的查询。 引出问题: 可能会存在主从延迟,导致主从一致性问题。查询主库的量级需要控制。数据量庞大,索引也占据存储空间,磁盘空间不足,当主库宕机后会影响所有模块的写入,需要进行数据分片,因此引出分库…

【个人博客搭建】(18)使用Quartz.NET 定时备份数据库

Quartz.NET在系统主要承担的一些关键功能&#xff1a; 任务调度&#xff1a;Quartz.NET 允许开发人员创建、调度和管理定时任务&#xff0c;支持简单触发器和Cron表达式等多样化的触发策略。灵活性&#xff1a;Quartz.NET 提供了灵活的任务安排机制&#xff0c;不仅支持基于时间…

JavaWeb入门-HTML

一、HTML 1.HTML 网络的骨架 超文本标记语言 ①超文本 图片、音频、视频、普通文本。。。 ②标记语言 语法&#xff1a;通过标签的形式展示 a.双标签 <html>内容</html> b.单标签 <br> 2.HelloWorld ①新建网页文件&#xff08;后…

《大数据最全面试题-Offer直通车》目录

大数据时代已经到来&#xff0c;数据科学家、大数据工程师、数据分析师等岗位成为了热门职业。如果你正准备面试&#xff0c;想要脱颖而出&#xff0c;那么《大数据最全面试题-Offer直通车》是你的不二选择。 本书汇集了多篇超过1万字的精华内容&#xff0c;包括程序员入职新公…