DCL锁双重检查;

本文深入探讨了在多线程环境下,如何使用Double Check Locking(DCL)解决单例模式中的并发问题。DCL通过两次检查和加锁确保线程安全,避免了不必要的锁竞争。同时,文章提到了CPU指令重排可能导致的线程安全问题,强调了保证数据最终一致性的重要性。内容涵盖了Java内存模型、单例模式实现、线程同步和程序优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学习来源于:B站马士兵视频:《计算机底层原理

DCL:Double check lock。

其本质是为了解决多线程中语句执行不确定性

你也不知道当前其他线程进行到哪里,哪里都有可能。

比如在单例模式中,需要判断单例对象是否已经存在,if如果单例存在:则返回单例。否则else就去new一个单例。

这样做有一个问题:当涉及到多线程时,可能有多个线程同时来判断单例是否存在,此时,有两个甚至多个线程同时判断为不存在,则多个线程都会去创建单例。这样还是很多对象。

如何避免,首先考虑加锁,即判断单例是否存在的这个操作,应该加到锁中,判断加锁这个操作,本身也要加锁。

即我们:

S1:先判断单例是否存在,(第一次check

S2:如果不存在,那我们就要加锁,准备执行创建对象这个过程。

S3:加锁完成后。注意:此时有可能有多个线程,同时判断check发现单例不存在,那么这个锁一旦解开,其他线程还是会进入锁区重新创建对象,所以,需要在锁区,进行第二次check判断单例对象是否存在。这个在锁定代码区中先进性的二次判断单例是否存在,就叫做:DCL。

S4:在锁区内部,第二次check之后,判断单例是否已经存在,如果存在,那么就直接返回,否则创建。

总结:

就是Check-Lock-Check


此时可能有新的疑问:第一次检查不要可以吗?

不可以:为了避免不必要的锁。假设有一万线程,然后有一个线程抢到了,那么锁释放后,后面其他9999个线程要挨个锁一遍,然后检查单例对象是否存在,然后再出锁。这个一进一检查再一出,是不必要的麻烦。

有了外面这个锁,那么一旦有一个线程new了之后,其他9999个线程,就不用再费劲了。



这样DCL之后就结束了吗?

还没有,CPU执行代码语句逐条指令执行的。

我们看起来是一条语句,但是在CPU中却是很多条指令

这个单单的一条语句: new obj = new object();

这条语句在CPU中可以分为以下几条指令完成:其中每一步都有可能被其他线程抢断

  •  第一条new:在内存中开辟空间。
    • 这个空间的大小取决于T,里面的内容在一开始初始化的时候,不同的语言(C/C++、Java/C#)等,处理方式不同。
    • 马老师说Java会给相应类型的默认值。重新赋值:就像分配了一片新的土地。这个土地初始里面种了什么东西,不用的语言处理方式不同。Java会像耕地一遍,重新犁一遍。
    • 所以Java的初始化会比C/C++要稍微慢一点。因为多了一个重新赋类型默认值的过程,这样也稍微安全了一点。有利有弊。
  • 第二条:dup,马老师未讲。
  • 第三条:一开始是变量默认值为0。默认值是new时候的值,代码中初始化为8,初始化在第三步执行。特殊调用T的init初始化方法,就是默认的构造方法。
  • 第四条:变量t是一个指针,要通过t能访问到这块堆空间。所以要把这个对象的地址赋值给t。
  • 第五条:完成。

以上概括起来:三板斧:一个小小的对象初始化就要:有三步来实现:

  1. 首先分配空间:new(),然后赋予成员属性默认值。(类型的default值。)
  2. 调用构造方法:T.<init>,然后赋予成员属性初始值。(构造函数中指定或者传入的值。)
  3. 建立关联:指针变量t指向对象所在的内存空间。 

这里涉及另外一个问题:不仅仅程序执行的时候顺序不确定指令执行时,也有指令重排的问题。

 上锁的代码和不上锁的代码能不能形成并发?能。

所以新的一个线程可以基于目前thread1的线程进度访问到成员属性的状态。即:当指令执行到重排的第二条:7 astore_1时,此时锁中的线程实现了对m = 0的赋值。外界线程可以读到当前单例半实例化状态。

锁定的代码只能保证:

  • 1、原子性。
  • 2、可见性。
  • 保证原子性更新完之后才可以对另外一段上锁代码可见最终状态。
    • 唯一不能保证的就是线程的有序性。
    • 所以锁的代码内部依然可能发生换顺序。

基于上面来看:好像什么代码的执行顺序都可以改变的样子?什么样的顺序不可以换?

  • 判断能不能换,主要一个原则:只要保障 单线程内 数据最终一致性
    • 比如:a = 0;b = 0;这两条指令哪个先执行,数据最终都一致,所以这两条指令顺序可以发生重排。
    • 再比如:x = 0;x = 5;这两条指令:不可以交换顺序。因为数据最终不一致了。
  • 上面指令,最终结果都是成员属性m = 8;所以顺序不影响结果。可以交换。

只要保障了线程最终数据一致性就可以交换顺序。

程序优化有两个级别:编译器级别的优化;CPU级别的优化。

无知者无畏:达可效应——丁宁克鲁格效应:越是啥都不会的,胆子越大。马老师希望大家走出自己的愚昧之谷,走上求知之路。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值