单例模式的双重加锁机制为啥要两次检查,第一次检查完不是已经知道了吗?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式的双重加锁机制为啥要两次检查,第一次检查完不是已经知道了吗?相关的知识,希望对你有一定的参考价值。
第一张图是单例模式的双重加锁机制。然而我在网上看到有一个解释如下图第二张所示,意思大概是假如把第一个判断去掉会发生什么,他的回答我本来就没搞懂了,然而我又有个新的疑问,如果只是把第二个判断去掉呢,既然第一遍已经检查出了是否为空,那为啥要在lock里检查第二遍呢,感觉有点多余。这个问题困扰了我好久,请各位大神们在看清楚我的疑问再为我解答,万分感谢!
第一张图:针对的是多线程场景如果两个线程,同时进行为空判断,都为true。此时如果没有lock+第二个判断,必然会创建两个实例。
第二张图:多线程场景可以搞定,但是性能太差
所有针对单例的访问,都需要被锁住,然后一个一个执行。性能太差了。追问
第二张图解释了去掉第一个判断的状况,而我想问的是,既然有第一个判断了,为啥锁里面还要加上第二个判断
追答线程执行,有快有慢。这里比如有两个线程A,B。
两个线程都同时经过了第一个判断。
此时,操作系统对线程进行调度,使得A线程挂起了。B线程正常执行。
过了一段时间,B线程执行完了,单例对象创建完毕了。A线程此时被唤醒了,可以执行了。
这时候,如果不进行2次判断(B线程早就创建了单例对象),必然会创建两个对象。所以要进行2次判断,防止闯将多个对象
我的java水平要是会模拟的话,应该不会在这里问这个问题了
我的java水平要是会模拟的话,应该不会在这里问这个问题了
追答那你的水平也很可能遇不到需要两个锁的情况
追问但是有些东西不是你遇不到就不去学的呀,有时候面试官会问原理,这个东西我百度不到才迫不得已在这里问的
设计模式。双重检查单例(优化到极致完美),解决单例懒汉式的线程不安全
上文讲到
单纯的加锁就可以
package com.yzdzy.design.singleton;
/**
* 加锁
*/
public class Mgr04
private static Mgr04 mInstance;
private Mgr04()
public static synchronized Mgr04 getInstance()
if (mInstance == null)
// 两个线程里面 会创建多个实例。
try
Thread.sleep(1);
catch (InterruptedException e)
e.printStackTrace();
mInstance = new Mgr04();
return mInstance;
public void m()
System.out.println("m");
public static void main(String[] args)
for (int i = 0; i < 10; i++)
new Thread(() ->
System.out.println(Mgr04.getInstance().hashCode());
).start();
but 效率低了对吗
那么有人尝试通过锁同步代码的方式去解决
package com.yzdzy.design.singleton;
public class Mgr05
private static Mgr05 mInstance;
private Mgr05()
public static Mgr05 getInstance()
if (mInstance == null)
// 妄图通过减小同步代码库的方式提高效率,然后不可行
synchronized (Mgr05.class)
try
Thread.sleep(1);
catch (InterruptedException e)
e.printStackTrace();
mInstance = new Mgr05();
return mInstance;
public void m()
System.out.println("m");
public static void main(String[] args)
for (int i = 0; i < 10; i++)
new Thread(() ->
System.out.println(Mgr05.getInstance().hashCode());
).start();
解决了吗
没有。
> Task :Mgr05.main()
1288032892
1696172314
1098833185
471895473
334817404
541982512
662136803
1722732360
1133601195
1313614725
因为他还是持有了懒加载的间隔 和最初的懒加载拥有相同的诟病
解决方案应势而生
双重检查
package com.yzdzy.design.singleton;
/**
* 懒汉式
* lazy loading
* 优点:按需初始化,什么时候用什么时候才初始化
* 缺点:线程不安全
*/
public class Mgr06
//jit volatile 解决指令重排
private static volatile Mgr06 mInstance;
private Mgr06()
public static Mgr06 getInstance()
if (mInstance == null)
// 妄图通过减小同步代码库的方式提高效率,然后不可行
synchronized (Mgr06.class)
if (mInstance == null)
try
Thread.sleep(1);
catch (InterruptedException e)
e.printStackTrace();
mInstance = new Mgr06();
return mInstance;
public void m()
System.out.println("m");
public static void main(String[] args)
for (int i = 0; i < 10; i++)
new Thread(() ->
System.out.println(Mgr06.getInstance().hashCode());
).start();
> Task :Mgr06.main()
1288032892
1288032892
1288032892
1288032892
1288032892
1288032892
1288032892
1288032892
1288032892
1288032892
以上是关于单例模式的双重加锁机制为啥要两次检查,第一次检查完不是已经知道了吗?的主要内容,如果未能解决你的问题,请参考以下文章