finally块是java中保障锁和资源安全释放的最后防线,无论是否发生异常、是否提前return或catch中抛新异常,它都能确保unlock()或close()执行;标准写法是lock.lock()在try外,unlock()在finally内,资源类优先用try-with-resources。

在Java中,finally块是保障锁和资源安全释放的最后防线。它不依赖于是否发生异常、是否提前return,甚至在catch块中抛出新异常时,仍能确保清理逻辑执行——这是synchronized做不到的,也是手动管理Lock或I/O资源时不可替代的机制。
为什么必须把unlock()放在finally里
Lock对象不会自动释放,必须显式调用unlock()。如果写在try块末尾,一旦业务代码抛出未捕获异常,程序直接跳转至catch或向上抛出,unlock就被跳过。后果是锁长期被持有,其他线程阻塞,最终引发死锁或线程饥饿。
- lock.lock()必须在try外部调用:加锁本身可能失败(如被中断),但释放逻辑不能因此被绕过
- 不能把unlock放进catch:catch只负责异常响应,若它自己也return或throw,仍会漏掉释放
- 避免在多个return路径中分散unlock:极易遗漏,且难以维护
标准写法:Lock + try-finally
最稳妥、最通用的方式如下:
Lock lock = new ReentrantLock();lock.lock(); // 加锁不在try内
try {
// 临界区:可能抛异常、return、continue等
doSomething();
} finally {
lock.unlock(); // 这里一定会执行
}
- 无需isHeldByCurrentThread()判断:ReentrantLock允许非持有线程unlock(虽不推荐),且该判断非原子,反而引入竞态风险
- 不要在finally里throw或return:否则可能覆盖原始异常或改变方法返回值
- 若lock为null,需在lock.lock()前做空检查,否则NPE发生在加锁阶段,不是释放阶段的问题
资源类(如流、连接)的finally处理要点
对FileInputStream、Connection等需close()的资源,finally中释放要更谨慎:
- 变量必须在try外声明并初始化为null,否则finally无法访问
- 每个资源单独判空 + 单独try-catch关闭:防止一个close()抛异常导致后续资源无法释放
- close()异常应捕获并忽略(或记日志),避免掩盖try块中的主异常
示例:
InputStream in = null;try {
in = new FileInputStream("data.txt");
// 读取操作
} catch (IOException e) {
// 处理业务异常
} finally {
if (in != null) {
try { in.close(); } catch (IOException ignored) {}
}
}
更优替代:优先用try-with-resources
对于实现了AutoCloseable的资源(如InputStream、Scanner、ResultSet等),try-with-resources是首选,它比手写finally更安全、简洁:
- JVM自动按“后声明先关闭”顺序调用close()
- 即使try块抛异常,close()异常会被压制(suppressed),主异常仍完整可见
- 资源作用域明确,无变量泄漏或访问不到问题
- Lock本身未实现AutoCloseable,如需类似效果,可自行包装为AutoCloseable工具类
示例:
try (FileInputStream fis = new FileInputStream("data.txt")) {int data = fis.read();
// 使用资源
} catch (IOException e) {
// 异常处理
} // fis在此自动关闭
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











