Java发生在之前和同步

我对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 之前 ,因为在同一个监视器上有同步,而其他线程仅在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 之前 ,就像之前一样。

  • 由于MyObject持有的ReentrantLock同步,其他线程之间的操作在4之间是可见的。

  • 4上的操作逻辑上发生在3之后,但是由于在不同的监视器上同步,因此在34之间没有发生关系。

  • 即使在unlock 4之后在sharedMonitor上存在同步,上述要点仍然适用。

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

问:我的理解是否正确?

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

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

这也意味着如果你将o传递给线程构造函数,你也不需要synchronized块(3)。

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

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

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

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

往上看。

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

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