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

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

问题描述

阅读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 类,例如

Furthermore, for a Holder class such as

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 写入一些静态变量

因为 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 写入一些静态变量
  3. 将 42 写入指针 1 的偏移量 0

可怕吗?是的,但它可能发生.

Scary? Yes but it can happen.

这意味着线程 2 现在可以在 n 获得值 42 之前调用 assertSanity.值 nassertSanity 期间被读取两次,一次在操作 #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天全站免登陆