try-catch-finally的执行顺序

来源:互联网 发布:数据挖掘技术有哪些 编辑:程序博客网 时间:2024/06/04 22:25

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 2T1 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 2T1 return 1
0 0
原创粉丝点击