单例DCL带出的问题和处理过程

单例DCL带出的问题和处理过程,第1张

单例DCL带出的问题和处理过程 一,问题的提出 1,原使用方式:DCL

DCL双重检查的问题是:指令重排的不确定

public class DoubleCheckedLocking {
    private static Resource resource;

    public static Resource getInstance() {
        if (resource == null) {//2
            synchronized (DoubleCheckedLocking.class) {
                if (resource == null)//1
                    resource = new Resource(); //第一个线程还没创建完,只是分配了内存,指了引用,线程执行上面,会判断resource != null,最终导致程序崩溃
            }
        }
        return resource;
    }

    static class Resource {

    }
}
2,个人理解的解决方式:volatile 

可以禁止指令重排,解决这个问题

感兴趣可以看这个: The "Double-Checked Locking is Broken" Declaration

public class DoubleCheckedLocking {
    private static volatile  Resource resource;

    public static Resource getInstance() {
        if (resource == null) {
            synchronized (DoubleCheckedLocking.class) {
                if (resource == null)
                    resource = new Resource(); 
            }
        }
        return resource;
    }

    static class Resource {

    }
}
3,SonarQube 建议使用:带出疑问

看不懂注释添加localResource 的用意,有大佬给讲讲么,能猜到的也就是效率提高

class ResourceFactory {
  private volatile Resource resource;

  public Resource getResource() {
    Resource localResource = resource;//add
    if (localResource == null) {
      synchronized (this) {
        localResource = resource;//add
        if (localResource == null) {
          resource = localResource = new Resource();//add
        }
      }
    }
    return localResource;
  }

  static class Resource {
  }
}
二,讨论过程

通过与各位大神的讨论,以下是整理:

A:
避免线程并发。

B:
为啥要放一个局部变量
改成final,直接在初始化的时候new,一了百了 (恶汉:实际工作中直接用饿汉式,不会差那么一个对象的内存)

RR:
你多个CPU每个有单独的cache和寄存器,但是内存共享的情况下呢?

X:
看一段代码是不是能出现优化,直接看字节码。实在看不出同步块里先赋值再判空,再new,赋值两次。这种傻逼写法能有啥优化的可

RR:
可能它考虑的是在共享内存的smp机(双CPU系统)上,每个cpu有自己的cache和寄存器,但是共享同一个系统内存,这个时候你的Resouerce 可能就保护不到了 你需要重新copy实例进行处理,   其实感觉这些你没必要深究它,人家官方都说了,这种jmm 的硬伤,都没法搞定,你又何必深究呢,他们不是推荐利用类加载器的lazyLoad 特性去处理问题嘛!

X:
首先得确定,这种写法是不是确实能产生优化
如果不能产生任何优化,那就是傻逼写法
如果确实有那么一点点优化,那就了解他到底是优化了什么

Y:
这个volatile localResource 是减少synchorinzed的执行,以及增加可见性

G:
应该是 volatile 修饰的 resource 需要锁总线
赋值给 localResource 后,使用localResource 就不用锁总线了
 

以上是讨论过程和扩展,我只是个发现者...

三,相关知识扩展 1,JVm创建对象三个步骤
public class Resource {
   private int  a;
}

1,给这个对象申请内存                       :a=0

2,给这个对象的成员变量初始化         :a=8

3,把这块内存的内容分配给这个对象  :栈内存指向a=8的地址

如果发生指令重排序,二三步颠倒了,半初始化的时候就赋值了,会发生还没有初始化的时候就把指向指定到a=0的地址,这个对象就不为空了,多线程读取的值是不同的。

相当于把半成品发布,虽然这个半成品会逐渐完善,但这个过程中充满了不确定。

2,synchroized

保证原子性,但是不能阻止重排序。

3,volatile

并不能保证多个线程共同修改变量时锁带来的不一致问题,都看到是同一个,但会同时做一样的事情,没有原子控制,多人做了重复的事情。(案例:多线程不用同步, *** 作一个volatile 修饰的变量。在 *** 作方法上添加同步可以解决)

4,SMP

SMP(Symmetrical Multi-Processing)说的是双CPU系统,对称多处理机系统中最常见的一种,通常称为2路对称多处理。它在普通的商业、家庭应用之中并没有太多实际用途,但在专业制作,如3DMaxStudio、Photoshop等软件应用中获得了非常良好的性能表现,是组建廉价工作站的良好伙伴。随着用户应用水平的提高,只使用单个的处理器确实已经很难满足实际应用的需求,因而各服务器厂商纷纷通过采用对称多处理系统来解决这一矛盾。在中国国内市场上这类机型的处理器一般以4个或8个为主,有少数是16个处理器。

四,总结,感悟

问题还是要根据实际情况去看的,我感觉解决一个问题得我同时必然会出现另一个问题,看自己实际情况的容忍度吧,或许某一个时刻计算机的发展,这些东西全部都不适用的也说不好,保持对知识的好奇和探索欲,这是我的感悟,希望能帮到遇上相同问题的人。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5637744.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存