当前位置: 首页 > news >正文

Java的双重检查锁机制(DCL)与懒加载的单例模式

目录

一、懒加载的单例模式。

二、常见的方案。(有缺陷)

(1)单纯的同步方法。(性能差)

(2)单次检查 + 同步块。(仍有线程安全问题)

三、双重检查锁机制方案。(完美解决)

(1)基础代码实现。

(2)测试双重检查锁机制+单例模式+多线程的正确性。


一、懒加载的单例模式。

  • 懒加载,就是延迟初始化单例对象不在类加载时创建,而是在第一次被使用时(即:第一次调用getInstance())才初始化

  • 好处:如果程序始终没用到这个单例,就不会创建它,节省内存和初始化资源(尤其适合创建成本高的对象)。
  • 单例模式的核心要求:任何情况下(包括多线程环境),都只能有一个实例被创建

  • 最直接的问题:多线程同时调用getInstance()方法,可能同时判断“实例未创建”,从而导致多个实例被创建(破坏单例)。
  • 解决方法:双重检查锁机制。(多线程也不惧,volatile+synchronized

二、常见的方案。(有缺陷)

(1)单纯的同步方法。(性能差)
package com.hyl;public class Singleton {private static Singleton instance;//线程退出同步方法后,instance赋值会同步到主内存中,保证其他线程看到最新值public static synchronized Singleton getInstance(){if(instance == null){instance = new Singleton();}return instance;}
}
  • 基本:保证线程安全(同一时间只有一个线程执行方法)。
  • 性能极差:即便实例已经创建,后续所有调用仍需排队获取锁。
(2)单次检查 + 同步块。(仍有线程安全问题)
package com.hyl;public class Singleton {private static Singleton instance;public static Singleton getInstance(){//第一次检查if(instance == null){synchronized (Singleton.class){//无第二次检查instance = new Singleton();}}return instance;}
}
  • 每个类在 JVM 中只会生成一个Class对象(全局唯一),且无论创建多少个该类的实例(如new Singleton() ),它们都共享同一个Class对象(Singleton.class)。
  • 当多个线程尝试进入 synchronized(Singleton.class) 同步块时,无论这些线程是操作该类的哪个实例,都必须竞争这把唯一的Class对象锁
  • 因为在单例模式中,getInstance()是静态方法(属于类而非实例),需要保证多线程环境下仅创建一个实例。使用了类级别的同步,由于Class对象的全局唯一性,这把锁的作用范围自然覆盖了整个类的所有实例,保证了单例模式的线程安全性

  • 问题:线程A与线程B同时通过第一次检查(且都发现instance == null),然后线程A先获取锁并创建了实例,当线程A释放锁后线程B会立马获取锁,此时虽然实例已经存在,但线程B并没有进行二次检查,会再次创建实例(破坏单例)。

三、双重检查锁机制方案。(完美解决)

(1)基础代码实现。
package com.hyl;public class Singleton {//必须使用volatile//禁止指令重排序,保证instance在完全初始化后才会被其他线程看到private static volatile Singleton instance;public static Singleton getInstance(){//第一次检查//如果instance不为空,则避免进入同步块if(instance == null){//只有在instance未创建时才加锁,减少锁竞争synchronized (Singleton.class){//第二次检查//避免多线程同时通过第一次检查if(instance == null){instance = new Singleton();}}}return instance;}
}
  • 实例只在第一次调用getInstance()创建(延迟初始化),符合懒加载。
  • 通过 “同步块 + 两次检查”,确保即使多线程同时进入,最终也只会创建一个实例。

  • volatile 关键字:双重检查锁必须配合 volatile 关键字!因为 instance = new Singleton();这行代码在 jvm 中可能会被拆分为三步:

  1. 为 instance 分配内存空间。

  2. 调用构造函数,初始化对象。

  3. 将 instance 指向内存空间。

  • 若没有 volatile,jvm 可能会对这步骤2、3进行执行重排序(优化执行效率)。导致线程A执行步骤3后,步骤2执行完前,线程B通过了第一次检查,认为 instance 已非空,直接返回一个未完全初始化的对象(引发错误)。
  • volatile的作用是禁止指令重排序,保证 instance 在完全初始化后才会被其他线程看到,进一步确保了懒加载单例的线程安全。
(2)测试双重检查锁机制+单例模式+多线程的正确性。
  • SingletonTest 测试类。(模拟20个线程获取instance)
  • 使用 CountDownLatch 协调多个线程之间的同步,允许一个或多个线程等待其他线程完成一系列操作后再继续执行。它是jdk自带的并发工具类,位于 java.util.concurrent 包下。
package com.hyl;import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;/*** 测试双重检查锁单例模式的正确、可用*/
public class SingletonTest {public static void main(String[] args) throws InterruptedException {//线程数int threadCount = 20;//协调多个线程执行之间同步CountDownLatch countDownLatch = new CountDownLatch(threadCount);//存储所有线程的获取的实例对象Set<Singleton> instances = new HashSet<>();//创建并启动线程for(int i = 0; i < threadCount; i++) {Thread thread = new Thread(()->{try {Thread.sleep(100);  //等待一会,确保多个线程几乎同时启动} catch (InterruptedException e) {e.printStackTrace();}//每个线程都获取单例实例Singleton instance = Singleton.getInstance();//HashSet 非线程安全,同步块能保证多线程对其 add() 操作的原子性、可见性synchronized (instances) {instances.add(instance);}//线程执行完毕,计数器减1countDownLatch.countDown();}, "thread-" + i);thread.start();  //启动线程}//等待所有线程执行完毕countDownLatch.await();//验证获取到的instance实例是否单例System.out.println("所有线程执行完毕!总实例数量:"+instances.size());if(instances.size() == 1){System.out.println("单例");}else{System.out.println("存在多个实例,非单例");}}
}
  • Singleton实体类。
package com.hyl;public class Singleton {//私有构造函数,只暴露静态方法获取实例private Singleton(){System.out.println("Singleton实例被创建,线程:"+Thread.currentThread().getName());}//必须使用volatile//禁止指令重排序,保证instance在完全初始化后才会被其他线程看到private static volatile Singleton instance;public static Singleton getInstance(){//第一次检查//如果instance不为空,则避免进入同步块if(instance == null){//只有在instance未创建时才加锁,减少锁竞争synchronized (Singleton.class){//第二次检查//避免多线程同时通过第一次检查if(instance == null){instance = new Singleton();}}}return instance;}
}
  • 执行结果如下。
  • 20个线程,总会有一个线程先竞争到创建实例的机会,其他的线程再来获取就直接拿到,无需重新创建新的实例。

http://www.dtcms.com/a/478687.html

相关文章:

  • Qt代码-QVector向量数组的增删改查插入统计复制
  • 深圳建设门户网站博客wordpress
  • 免费做网站的好不好大理悦花轩客栈在哪些网站做推广
  • 语义与认知中的循环解释悖论及其对人工智能自然语言处理深层语义分析的影响与启示
  • 购物类网站百度关键词搜索排名
  • 微信公众号的跳转网站怎么做外贸网站要先备案吗
  • 【VSCode+WSL】开发环境随身携带:我的VSCode+cpolar远程工作站实战
  • 长沙做最好网站东营建设信息网的网址
  • Kubernetes Pod 全面详解(基础 + 进阶)
  • JAVA算法练习题day40
  • 电子电气架构 --- 车载多系统架构
  • JVM 垃圾回收算法
  • 宁波企业如何建网站网站 提示危险
  • 嵌入式开发--STM32H7系列的硬件SPI的读写函数问题
  • printk 使用技巧
  • 深度学习入门(六)——模块、正则化与工程调优全解析
  • python高级05——HTTP协议和静态服务器
  • 现在网站一般做多大的南沙网站建设哪家好
  • 使用Mathematica做Lorenz系统的稳定性分析
  • centos升级redis至最新版(绿色版)
  • 做logo宣传语的网站电影网页设计素材
  • 从C++开始的编程生活(11)——string类基本语法和string类的基本实现
  • 南宁网站建设策划外包培训机构营销方案
  • 建站用什么搭建比较好网站后台是什么
  • 官方网站开发与定制广州网道营销广告有限公司
  • 设置 windows nginx.exe 每天 重启
  • 优先级队列(堆)-703.数据流中的最大值-力扣(LeetCode)
  • 亚马逊自养号测评新手从零起步:环境搭建全流程指南
  • 数据结构_栈和队列
  • 江苏优化网站关键词wordpress子域