返回是否最终“发生”?

2022-09-01 10:48:06

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

但是,我没有看到任何明确说明最终发生在返回之前的事情,所以呢?或者,是否有某种方法可以使编译器对子句重新排序,因为它只是日志记录。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()-

请注意,这与最终胜过回归的问题并不完全相同。我不是在问将返回什么,而是关于内存一致性和执行顺序。


答案 1

首先发生调用。然后最后阻止。然后控制离开函数(即 )。queryDatabase()return


答案 2

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

带有表达式的 return 语句尝试将控制权转移给包含它的方法的调用方;表达式的值将成为方法调用的值。更准确地说,执行此类 return 语句首先计算表达式。如果表达式的计算由于某种原因突然完成,则返回语句会由于该原因而突然完成。如果表达式的计算正常完成,生成值 V,则 return 语句突然完成,原因是返回值为 V。如果表达式的类型为 float 且不是 FP 严格 (§15.4),则该值可以是 float 值集或浮点扩展指数值集 (§4.2.3) 的元素。如果表达式的类型为 double 且不是 FP 严格表达式,则该值可以是 double 值集或双精度扩展指数值集的元素。

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

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