不是线程安全的对象发布 [英] Not thread-safe Object publishing

查看:88
本文介绍了不是线程安全的对象发布的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在阅读"Java并发实践"时,第3.5节中有此部分:

Reading "Java Concurrency In Practice", there's this part in section 3.5:

public Holder holder;
public void initialize() {
     holder = new Holder(42);
}

除了创建两个Holder实例的明显的线程安全隐患外,该书还声称可能会发生发布问题.

Besides the obvious thread safety hazard of creating two instances of Holder, the book claims a possible publishing issue can occur.

此外,对于Holder类,例如

public Holder {
    int n;
    public Holder(int n) { this.n = n };
    public void assertSanity() {
        if(n != n)
             throw new AssertionError("This statement is false.");
    }
}

可以抛出AssertionError

这怎么可能?我能想到的唯一允许这种荒谬行为的方法是,如果Holder构造函数不会被阻塞,那么当构造函数代码仍在另一个线程中运行时,将创建对实例的引用.

How is this possible? The only way I can think of that can allow such ridiculous behavior is if the Holder constructor would not be blocking, so a reference would be created to the instance while the constructor code still runs in a different thread.

这可能吗?

推荐答案

之所以可行,是因为Java的内存模型较弱.它不保证读写顺序.

The reason why this is possible is that Java has a weak memory model. It does not guarantee ordering of read and writes.

这个特殊的问题可以通过以下代表两个线程的两个代码片段来重现.

This particular problem can be reproduced with the following two code snippets representing two threads.

线程1:

someStaticVariable = new Holder(42);

线程2:

someStaticVariable.assertSanity(); // can throw

表面上似乎不可能发生这种情况.为了理解为什么会发生这种情况,您必须克服Java语法,然后再降到更低的水平.如果您看一下线程1的代码,则基本上可以将其分解为一系列的内存写和分配:

On the surface it seems impossible that this could ever occur. In order to understand why this can happen, you have to get past the Java syntax and get down to a much lower level. If you look at the code for thread 1, it can essentially be broken down into a series of memory writes and allocations:

  1. 分配给指针1的内存
  2. 将42写入指针1的偏移量为0
  3. 将指针1写入someStaticVariable

由于Java的内存模型较弱,因此从线程2的角度来看,代码完全有可能按以下顺序实际执行:

Because Java has a weak memory model, it is perfectly possible for the code to actually execute in the following order from the perspective of thread 2:

  1. 分配给指针1的内存
  2. 将指针1写入someStaticVariable
  3. 将42写入指针1的偏移量为0

吓人?是的,但是有可能发生.

Scary? Yes but it can happen.

这意味着线程2现在可以在n获得值42之前调用assertSanity.在assertSanity期间,有可能在操作#之前两次读取值n. 3完成并在此之后执行一次,因此会看到两个不同的值并引发异常.

What this means though is that thread 2 can now call into assertSanity before n has gotten the value 42. It is possible for the value n to be read twice during assertSanity, once before operation #3 completes and once after and hence see two different values and throw an exception.

编辑

根据乔恩·斯基特(Jon Skeet)AssertionError可能仍然发生 Java 8 除非字段是最终的.

According to Jon Skeet, the AssertionError migh still occur with Java 8 unless the field is final.

这篇关于不是线程安全的对象发布的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆