DCL指令重排序问题

 2024-01-14    0 条评论    722 浏览

多线程

概念

DCL是double check lock的缩写,对锁的双重判定

比如单例模式的懒汉式:

  • 为了多线程安全加锁以及锁内部非空判断
  • 为了让锁不影响多线程性能,则在锁前也加上非空判断。 =

例如

    private static DCL dcl;

    private DCL() {
    }

    public static DCL get() {
        if (dcl == null) {
            synchronized (DCL.class) {
                if (dcl == null) {
                    dcl = new DCL();
                }
            }
        }
        return dcl;
    }

DCL问题

指令重排序

实例化对象出现之灵重排序,导致的属性为空的情况

对象的实例化包含三个步骤:

  1. 对象半初始化(为类初始化分配内存空间)
  2. 调用构造函数为实例化对象属性赋值
  3. 对象创建关联(将对象指向分配的内存空间)

其中第二步骤与第三步是会互换的。

当出现先创建对象关联,再对实例化对象赋值的时候,后来的线程获取对象,就会出现获取实例化对象中,属性为空的一瞬间。

例如下面代码

public class DCL_Volatile {

    private static DCL_Volatile dcl;
    private String type;

    private DCL_Volatile(String type) {
        this.type = type;
    }

    public static DCL_Volatile get() {
        if (dcl == null) {
            synchronized (DCL_Volatile.class) {
                if (dcl == null) {
                    dcl = new DCL_Volatile("type");
                }
            }
        }
        return dcl;
    }

    public String getType() {
        return type;
    }
}

解决(DCL增加volatile关键字)

加上volatile禁止指令重排序

public class DCL_Volatile {

    private volatile static DCL_Volatile dcl;
    private String type;

    private DCL_Volatile(String type) {
        this.type = type;
    }

    public static DCL_Volatile get() {
        if (dcl == null) {
            synchronized (DCL_Volatile.class) {
                if (dcl == null) {
                    dcl = new DCL_Volatile("type");
                }
            }
        }
        return dcl;
    }

    public String getType() {
        return type;
    }
}

模拟指令重排序

测试环境中,很难复现指令重排序的情况。

这里直接在代码中,增加实例化对象赋值的操作,模拟指令重排序出现的问题。

    private static DCL dcl;
    private String type;

    private DCL() {
    }

    public static DCL get() {
        if (dcl == null) {
            synchronized (DCL.class) {
                if (dcl == null) {
                    dcl = new DCL();
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    dcl.type = "赋予参数";
                }
            }
        }
        return dcl;
    }

上面这段代码,多线程执行的时候,就会出现部分线程获取到实例对象,但是对象属性为空的情况。

而且这种情况在懒加载中很常见,并且无法用volatile解决。

若实例化后的其他步骤带有赋值操作,且一定要用DCL模式的话,则需要在外部判断的时候,增加属性值判断。