有没有办法显式启用/禁用Java编译器重新排序指令?

时间:2016-06-29 19:29:15

标签: java multithreading

我正在学习Java并发,并且知道以下单例不是完全线程安全的。由于指令重新排序,线程可能在初始化之前获取实例。防止此潜在问题的正确方法是使用volatile关键字。

public class DoubleCheckedLocking {
    private static Instance instance;
    public static Instance getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedLocking.class) {
                if (instance == null)
                    instance = new Instance();
            }
        }
        return instance;
    }
}

我尝试在没有volatile关键字的情况下重现潜在问题,并编写了一个演示,表明使用上面的代码可能会在多线程环境中导致NullPointerException。但是我没有找到一种方法来明确地让Java编译器执行指令重新排序,并且我使用上面的单例的演示总是很好地运行而没有任何问题。

所以我的问题是如何显式启用/禁用Java编译器重新排序指令或如何在双重检查的锁定单例中使用volatile关键字来重现问题?

2 个答案:

答案 0 :(得分:2)

这里危险的事情不一定,其他线程可能会从null收到getInstance作为答案。危险的是,他们可能会观察到一个尚未正确初始化的实例。

要检查这一点,请在您的单身人士中添加几个字段,例如:

class Singleton {

    private List<Object> members;

    private Singleton() {
        members = new ArrayList<>();
        members.addAll(queryMembers());
    }

    private Collection<Object> queryMembers() {
        return Arrays.asList("Hello", 1, 2L, "world", new Object());
    }

    public int size() {
        return members.size();
    }

    private static Singleton instance = null;

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

这被称为&#34;不安全的出版物&#34;。其他线程可能会看到单个实例部分初始化(即,members字段可能仍为null,或者列表可能为空,或者只是部分填充,或更糟:由于一个不一致的状态刚被添加的对象。)

在上面的示例代码中,size的外部调用者不应该看到与5不同的值,对吧?我没有尝试过,但如果时间不对,呼叫者可以观察到不同的值,我不会感到惊讶。

原因是,允许编译器翻译

instance = new Singleton();

成为

的内容
instance = allocate_instance(Singleton.class);   // pseudo-code
instance.<init>();

因此,我们有一个窗口,其中instance不再是null,但实际对象尚未正确初始化。

The "Double-Checked Locking is Broken" Declaration对此进行了深入的解释。

答案 1 :(得分:0)

这是Java Concurrency in Practice书的摘录:

  

调试提示:对于服务器应用程序,请务必始终指定   -server JVM命令行在调用JVM时切换,甚至用于开发和测试。服务器JVM执行更多优化   比客户端JVM,例如从循环中提取变量   没有在循环中修改;可能似乎在   开发环境(客户端JVM)可以在部署中中断   环境(服务器JVM)。例如,如果我们“忘记”宣布   清单3.4中的变量asleep as volatile,服务器JVM可以   将测试从环路中提升(将其转换为无限循环),但是   客户端JVM不会。出现的无限循环   开发成本远远低于仅出现的开发成本   生产

所以你可以尝试一下。但是没有100%可靠的方法来启用重新排序。