Java发生在之前和同步

时间:2013-06-19 12:45:45

标签: java multithreading synchronized happens-before

我对Java的和同步之前发生了一些分歧。

想象一下以下场景:

主线程

MyObject o = new MyObject();    // (0)
synchronized (sharedMonitor) {
    // (1) add the object to a shared collection
}
// (2) spawn other threads

其他主题

MyObject o;
synchronized (sharedMonitor) {
    // (3) retrieve the previously added object
}
// (4) actions to modify the object

请注意,MyObject的实例变量既不是volatile,也不是finalMyObject的方法不使用同步。

我的理解是:

  • 1 发生在 3 之前,因为同一台显示器上存在同步,而其他线程仅在< strong> 2 ,在 1 之后执行。

  • 4 上的操作无法保证以后对主线程可见,除非所有线程都进一步同步,并且主线程在这些操作后以某种方式同步。

问:是否可以保证 0 上的操作在 3 上可见,之前发生,并发访问,或者我必须将变量声明为volatile


现在考虑以下情况:

主线程

MyObject o = new MyObject();    // (0)
synchronized (sharedMonitor) {
    // (1) add the object to a shared collection
}
// (2) spawn other threads, and wait for their termination
// (5) access the data stored in my object.

其他主题

MyObject o;
synchronized (sharedMonitor) {
    // (3) retrieve the previously added object
}
o.lock();    // using ReentrantLock
try {
    // (4) actions to modify the object
} finally { o.unlock(); }

我的理解是:

  • 1 发生在 3 之前,就像之前一样。

  • 由于ReentrantLock所持有的MyObject上的同步,其他线程之间可以看到 4 上的操作。

  • 4 上的操作逻辑上发生在 3 之后,但 3 之前没有发生 - 之前的关系>至 4 ,因为在不同的监视器上进行同步。

  • 即使sharedMonitor 4 之后unlock上有同步,上述要点仍然有效。

  • 4 上的操作不会 5 之前发生,即使主线程等待其他任务终止。这是由于 5 上的访问未与o.lock()同步,因此主线程仍可能看到过时的数据。

问:我的理解是否正确?

1 个答案:

答案 0 :(得分:9)

  

问:是否保证0处的操作可见,3之前发生,并发访问,或者我必须将变量声明为volatile?

是的,有保证。你需要在主线程中有synchronized块,因为在线程启动时有一个before-before关系。从JLS 17.4.5开始:“在启动线程中的任何操作之前发生对线程的start()调用。”

这也意味着如果将o传递给线程构造函数,则不需要{3}}周围的synchronized块。

  

对(4)的动作在逻辑上发生在(3)之后,但是在(3)到(4)之间没有发生关系,因为在不同的监视器上进行同步。

是和否。逻辑顺序意味着在相同的线程中,即使它是不同的监视器,也肯定存在先发生关系。即使编译器处理不同的监视器,编译器也无法重新排序4到4。访问volatile字段也是如此。

对于多个线程,因为(3)只读取对象,所以没有竞争条件。但是,如果(3)正在对对象进行修改(而不是仅仅读取它),那么在另一个线程中,这些修改可能在(4)处看不到。正如你引用和@StephenC重申的那样,JLS说只有同一台监视器才能保证之前发生的关系。 JLS 17.4.5:“监视器上的解锁发生在该监视器上的每个后续锁定之前。”

  

即使在(4)解锁后sharedMonitor上存在同步,上述要点仍然适用。

见上文。

  

(4)上的操作不会发生 - 在访问(5)之前,即使主线程等待其他任务终止

没有。一旦主线程调用thread.join()并且它返回而没有被中断,那么主线程将与它所连接的线程的内存完全同步。在连接的线程和执行连接的线程之间存在一个发生在之前的关系。 JLS 17.4.5:“线程中的所有操作都发生在任何其他线程从该线程上的join()成功返回之前。”