Java - CountDownLatch.await() 能否被编译器重新排序
Java - Can CountDownLatch.await() be reordered by the compiler
我必须在不同的系统上调用一个操作。另一个系统是高并发和分布式的。因此我将它集成到 MessageBus 上。需要一种实现让调用者等待,直到在总线上收到结果或超时到期。
实现包括三个步骤:首先,我必须将 future 和 CountDownLatch(一旦收到结果就会释放)打包到 ConcurrentHashMap 中。该 Map 与监听 MessageBus 的组件共享。然后就要开始执行了,最后在latch上等待。
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 个步骤吗?据我了解,只有第1步和第3步形成了happens-before关系。所以理论上步骤 2 可以由编译器自由移动。是否可以将步骤 2 移到 await 之后,从而使代码无效?
后续问题:在共享对象上用 wait/notify 组合替换 CountDownLatch 是否可以解决问题而无需进一步同步(当然不考虑超时)
如果最终结果保持不变(就编译器而言),则可能会发生指令重新排序,如果涉及多个线程,则可能需要额外的同步(因为编译器不知道其他线程可能需要特定的订单)。
在单个线程中,如您的示例所示,所有步骤彼此之间具有 happens-before
关系。即使 那些方法调用,最终结果也需要相同(execute()
在 await()
之前调用)。由于 await()
涉及同步,因此 execute()
不可能以某种方式在它之后滑动,除非您使用的是 buggy 疯狂的实现。
countDown()
和 await()
之间的 happens-before
关系确保 PendingExecution
代码发生在代码执行 await()
之前。所以显示的代码没有任何问题。
你应该总是更喜欢java.util.concurrent
类而不是wait/notify
,因为它们更容易使用并且提供更多的功能.
我必须在不同的系统上调用一个操作。另一个系统是高并发和分布式的。因此我将它集成到 MessageBus 上。需要一种实现让调用者等待,直到在总线上收到结果或超时到期。 实现包括三个步骤:首先,我必须将 future 和 CountDownLatch(一旦收到结果就会释放)打包到 ConcurrentHashMap 中。该 Map 与监听 MessageBus 的组件共享。然后就要开始执行了,最后在latch上等待。
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 个步骤吗?据我了解,只有第1步和第3步形成了happens-before关系。所以理论上步骤 2 可以由编译器自由移动。是否可以将步骤 2 移到 await 之后,从而使代码无效?
后续问题:在共享对象上用 wait/notify 组合替换 CountDownLatch 是否可以解决问题而无需进一步同步(当然不考虑超时)
如果最终结果保持不变(就编译器而言),则可能会发生指令重新排序,如果涉及多个线程,则可能需要额外的同步(因为编译器不知道其他线程可能需要特定的订单)。
在单个线程中,如您的示例所示,所有步骤彼此之间具有 happens-before
关系。即使 execute()
在 await()
之前调用)。由于 await()
涉及同步,因此 execute()
不可能以某种方式在它之后滑动,除非您使用的是 buggy 疯狂的实现。
countDown()
和 await()
之间的 happens-before
关系确保 PendingExecution
代码发生在代码执行 await()
之前。所以显示的代码没有任何问题。
你应该总是更喜欢java.util.concurrent
类而不是wait/notify
,因为它们更容易使用并且提供更多的功能.