try-catch-finally的执行顺序
简化的例子
try-catch-finally中finally块中的代码会在try块中的return语句前执行,现在考虑在try和finally中对同一变量进行值修改的情况,代码如下:
public class TempMain {
public static int id =0;
public static void main(String[] args){
System.out.println("返回:"+changeid());
System.out.println("最终id:"+id);
}
public static int changeid() {
try {
id = 1;
return id;
} catch (Exception e) {
return id;
} finally {
id =2;
}
}
}
执行结果为
返回:1
最终id:2
可见,虽然在函数返回结果前,finally块中的代码将id的值修改为了2,但函数的返回结果未受影响,仍然返回了1。
用javap查看这段代码的字节码,得到:
其中 Locals = 3 表示本地变量的slot个数(编号0-2)
行号 | 指令 | 含义 |
---|---|---|
0 | iconst1 | 将int类型常量值1推到栈顶 |
1 | putstatic | 将栈顶的赋值给静态常量id |
4 | getstatic | 访问(取得)静态变量id的值 |
7 | istore_2 | 将当前值存入本地变量的 |
8 | iconst_2 | 将int类型常量值2推到栈顶 |
9 | putstatic | 将栈顶的赋值给静态常量id |
12 | iload_2 | 将第3个slot中的本地变量加载到栈顶 |
13 | ireturn | 返回栈顶值 |
可以看到,虽然有将静态变量id赋值为2的操作,但真正返回的是在执行finally块之前就存在第三个slot中的“1”。
将代码简单更改,在finally中也添加return语句
public class TempMain {
public static int id =0;
public static void main(String[] args){
System.out.println("返回:"+changeid());
System.out.println("最终id:"+id);
}
public static int changeid() {
try {
id = 1;
return id;
} catch (Exception e) {
return id;
} finally {
id =2;
return id;
}
}
}
执行结果为
返回:2
最终id:2
来看看这次生成的字节码
行号 | 指令 | 含义 |
---|---|---|
0 | iconst1 | 将int类型常量值1推到栈顶 |
1 | putstatic | 将栈顶的赋值给静态常量id |
4 | goto 12 | 执行行号12的操作 |
12 | iconst_2 | 将int类型常量值2推到栈顶 |
13 | putstatic | 将栈顶的赋值给静态常量id |
16 | getstatic | 访问(取得)静态变量id的值 |
19 | ireturn | 返回栈顶值 |
这一次,由于finally中含有return语句,因此直接将静态变量id的最新值返回给了调用方。
并发的例子
前面简化的例子,证明了return语句最好写在try中,除非你确实想在finally中对返回结果再进行修改。来看看try-catch-finally对并发结果的影响。
public class TempMain {
public static int id =0;
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args){
Thread thread1 = new Thread(new Runnable(){
@Override
public void run() {
System.out.println("T1 return "+changeid(1));
}});
Thread thread2 = new Thread(new Runnable(){
@Override
public void run() {
System.out.println("T2 return "+changeid(2));
}});
thread1.start();
thread2.start();
}
@SuppressWarnings("finally")
public static int changeid(int nid){
lock.lock();
try{
id = nid ;
}catch(Exception e){
return id;
}finally{
lock.unlock();
if(nid==1){
try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return id;
}
}
}
这段代码中,两个线程调用了同一个函数,该函数会修改静态变量id的值,并且如果是线程1调用的话,会sleep 5秒。按照期望,线程1将得到返回值1,线程2将得到返回值2,但运行的结果:
T2 return 2
T1 return 2
原因是,将return语句写在了finally中,此时,先执行的线程1已经放弃了锁,但由于需要sleep 5秒,因此并没有返回结果。但这时线程2得到了锁并修改了id的值,之后,线程1 sleep结束,返回结果。还记得前面例子中第二段代码么?返回的不是slot中的值,而是反回了getstatic的值,也就是id的值。因此,线程1的返回值是id的最新值—2。
为了避免这种由于过早释放锁导致返回结果被修改的情况,办法就是保持将return写在try中的习惯
public class TempMain {
public static int id =0;
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args){
Thread thread1 = new Thread(new Runnable(){
@Override
public void run() {
System.out.println("T1 return "+changeid(1));
}});
Thread thread2 = new Thread(new Runnable(){
@Override
public void run() {
System.out.println("T2 return "+changeid(2));
}});
thread1.start();
thread2.start();
}
@SuppressWarnings("finally")
public static int changeid(int nid){
lock.lock();
try{
id = nid ;
return id;
}catch(Exception e){
return id;
}finally{
lock.unlock();
if(nid==1){
try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
这一次得到正确的结果,因为线程1返回的是存在slot(本地变量)中的值。
T2 return 2
T1 return 1