Java-编译器可以重新排序CountDownLatch.await()



我必须在不同的系统上调用一个操作。另一个系统是高度并发和分布式的。因此,我通过MessageBus将其集成。一种实现需要让调用者等待,直到在总线上收到结果或超时到期。实现由三个步骤组成:首先,我必须将future和CountDownLatch打包到ConcurrentHashMap中,后者在收到结果后发布。此映射与在MessageBus上侦听的组件共享。然后我必须开始执行,最后等待闩锁。

public FailSafeFuture execute(Execution execution,long timeout,TimeUnit timeoutUnit) {
//Step 1
final WaitingFailSafeFuture future = new WaitingFailSafeFuture();
final CountDownLatch countDownLatch = new CountDownLatch(1);
final PendingExecution pendingExecution = new PendingExecution(future, countDownLatch);
final String id = execution.getId();
pendingExecutions.put(id, pendingExecution); //ConcurrentHashMap shared with Bus
//Step 2
execution.execute();
//Step 3
final boolean awaitSuccessfull = countDownLatch.await(timeout, timeoutUnit);
//...
return future;
}

所以问题是:这3个步骤能被编译器重新排序吗?据我所知,只有第一步和第三步形成了一段发生在关系之前的关系。因此,在理论上,编译器可以自由移动步骤2。步骤2是否会被移到等待之后,从而使代码无效?

后续问题:在共享对象上用等待/通知组合替换CountDownLatch是否可以在没有进一步同步的情况下解决问题(当然不包括超时(

如果最终结果保持不变(编译器认为(,则可能会发生指令重新排序,如果涉及多个线程,则可能需要额外的同步(因为编译器不知道其他线程可能需要特定的顺序(。

在单个线程中,如您的示例中所示,所有步骤之间都具有happens-before关系。即使编译器可以重新排序这些方法调用,最终结果也需要相同(在await()之前调用execute()(。由于await()涉及同步,execute()不可能在它之后以某种方式滑动,除非您使用的是bug疯狂的实现。

countDown()await()之间的happens-before关系确保了PendingExecution代码发生在代码执行await()之前。因此,显示的代码没有任何问题。

您应该始终更喜欢java.util.concurrent类而不是wait/notify类,因为它们更易于使用并提供更多功能。

最新更新