31

我试图说服自己finally子句中采取的行动发生在函数返回之前(在内存一致性意义上)。从JVM 规范中可以清楚地看出,在线程中,程序顺序应该驱动发生在关系之前 - 如果a在程序顺序中发生b ,则a 发生在 b之前。

但是,我没有看到任何明确说明最终在返回之前发生的事情,是吗?或者,编译器是否可以通过某种方式重新排序finally子句,因为它只是记录日志。

激励示例:我有一个线程从数据库中获取对象并将它们放入 ArrayBlockingQueue,而另一个线程正在将它们取出。我有一些用于事件计时tryfinally块,并且我在日志语句之前看到了返回的影响

线程 1:

public Batch fetch() {
    try {
        log("fetch()+");
        return queryDatabase();
    }
    finally {
        log("fetch()-");
    }
     ...
    workQueue.put(fetch());

线程 2:

log("take()+");
Batch b = workQueue.take();
log("take()-");

令我惊讶的是,它以意想不到的顺序打印出来。虽然,是的,不同线程中的日志记录语句可能会出现乱序,但至少有 20 毫秒的时间差。

124 ms : take()+
224 ms : fetch()+
244 ms : take()-
254 ms : fetch()-

请注意,这与finally trump return的问题并不完全相同。我不是在问将返回什么,而是在问内存一致性和执行顺序。

4

4 回答 4

20

调用首先queryDatabase()发生。然后是finally块。然后控制离开功能(即return)。

于 2011-04-18T21:01:48.060 回答
18

@David Heffernan 有正确的答案。JLS 规范在 14.17 节中讨论了 return 语句的行为(包括它如何与 finally 块交互)。从那里复制(强调我的):

带有 Expression 的 return 语句试图将控制权转移给包含它的方法的调用者;表达式的值成为方法调用的值。更准确地说,执行这样的 return 语句首先评估 Expression. 如果 Expression 的评估由于某种原因突然完成,那么 return 语句会因为这个原因而突然完成。如果表达式的评估正常完成,产生一个值 V,那么 return 语句突然完成,原因是一个返回值 V。如果表达式是浮点类型并且不是 FP-strict(第 15.4 节),那么该值可以是浮点值集或浮点扩展指数值集(第 4.2.3 节)的元素。如果表达式是 double 类型并且不是 FP-strict,则该值可以是 double 值集或 double-extended-exponent 值集的元素。

可以看出,return 语句总是突然完成。

前面的描述说“尝试转移控制”而不仅仅是“转移控制”,因为 如果方法或构造函数中有任何 try 语句(第 14.20 节),其 try 块包含 return 语句,那么这些 try 语句的任何 finally 子句将在将控制权转移到方法或构造函数的调用者之前,按从内到外的顺序执行。finally 子句的突然完成可能会中断由 return 语句启动的控制转移。

于 2011-04-18T21:34:45.543 回答
3

finally无论try块的结果或行为是什么,该子句都应执行,因此finallyreturn.

于 2011-04-18T21:03:15.647 回答
2

如果您只使用一个线程,您应该看到“take+, fetch+, fetch-, take-”。在您的示例中,它是多线程的,因此您不确定首先会发生什么。

于 2011-04-18T21:07:16.603 回答