2017-09-16 69 views
1

下阅读对象的字段这个职位是阅读后提出:https://shipilev.net/blog/2016/close-encounters-of-jmm-kind/#pitfall-semi-sync阅读对象的引用和JMM

class Box { 
    int x; 
    public Box(int v) { 
    x = v; 
    } 
} 

class RacyBoxy { 
    Box box; 

    public synchronized void set(Box v) { 
    box = v; 
    } 

    public Box get() { 
    return box; 
    } 
} 

和测试:

@JCStressTest 
@State 
public class SynchronizedPublish { 
    RacyBoxy boxie = new RacyBoxy(); 

    @Actor 
    void actor() { 
    boxie.set(new Box(42)); // set is synchronized 
    } 

    @Actor 
    void observer(IntResult1 r) { 
    Box t = boxie.get(); // get is not synchronized 
    if (t != null) { 
     r.r1 = t.x; 
    } else { 
     r.r1 = -1; 
    } 
    } 
} 

笔者说,这是可能的,r.r1 == 0 。我同意 那。但是,我对一个解释感到困惑:

实际的故障来自事实,即读取对象的引用并读取对象的字段在内存模型下是不同的。

我同意

阅读对象的引用和阅读对象的字段是内存模型 下不同,但我不知道它是如何对结果产生影响。

请帮我理解它。

P.S.如果有人对@Actor感到困惑。它只是意味着:在一个线程中运行。

回答

1

我认为它讲述了阅读有关顺序整合的代码的人们的一个常见的miconception。对一个实例的引用在一个线程中可用这一事实并不意味着它的构造函数已被设置。换句话说:读取实例与读取实例字段的操作不同。许多人认为,一旦他们可以观察一个实例,它需要运行构造函数,但由于缺少读取同步,上述示例并不适用。

+0

那么,你的意思是? 第一个线程(actor)发布了引用,但对象没有初始化是。然后,观察者读取box('boxie.get()')的引用,并且引用不是'null',观察者读取't.x == 0'。 – Gilgamesz

+0

是的,发布实例并调用其构造函数并不总是原子的,除非您使对象安全发布。 –

1

生病稍稍这里增加接受的答案 - 没有一些障碍是完全无法保证一旦你看到一个参考(觉得有些线程可以得到一个参考的持有) - 所有这一切构造领域被初始化。如果我没有弄错的话,我实际上已经在前一段时间回答了你的一个问题。

在构造函数的最后字段LoadLoad和​​之间插入两个障碍;它,你想想自己 - 你会发现,没有任何操作后的构造函数可以被重新排序与一个它:

Load -> Load (no Load can be re-ordered with a previous Load) 
Load -> Store (no Store can be re-ordered with a previous Load) 

另外请注意,这将是不可能为你打破在目前的x86内存模型下 - 因为它是(太?)强大的内存模型;因此这些障碍是免费x86 - 它们根本不被插入,因为操作不会被重新排序。

+0

感谢@Eugene的额外观点。 – Gilgamesz