一,问题的提出
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个处理器。
四,总结,感悟
最后这样修改了,因为要是按照他说的格式改,又会说volatile 影响效率。。。
public class DoubleCheckedLocking {
private static Resource resource;
public static synchronized Resource getInstance() {
if (resource == null)
resource = new Resource();
}
return resource;
}
static class Resource {
}
}
问题还是要根据实际情况去看的,我感觉解决一个问题得我同时必然会出现另一个问题,看自己实际情况的容忍度吧,或许某一个时刻计算机的发展,这些东西全部都不适用的也说不好,保持对知识的好奇和探索欲,这是我的感悟,希望能帮到遇上相同问题的人。