【问题标题】:Is opening several resources in one try-statement unreliable? Why?在一个尝试语句中打开多个资源是否不可靠?为什么?
【发布时间】:2026-02-09 13:45:01
【问题描述】:

这个程序:

import java.util.*;

public class Main {

    static Set<String> openResources = new TreeSet<>();

    static class MyResource implements AutoCloseable {

        boolean close;
        MyResource encapsulatedResource;
        String name;

        MyResource(String id, boolean exceptionOnCreate, boolean exceptionOnClose, MyResource encapsulatedResource) {
            this.close = exceptionOnClose;
            this.encapsulatedResource = encapsulatedResource;
            this.name = id;
            if (exceptionOnCreate) {
                throw new RuntimeException("Exception when creating " + id);
            }
            openResources.add(id);
            System.out.println(name + " is now open");
        }

        @Override
        public void close() {
            if (close) {
                throw new RuntimeException("Exception when closing " + name);
            }
            if (encapsulatedResource != null) {
                encapsulatedResource.close();
            }
            openResources.remove(name);
            System.out.println(name + " was successfully closed");
        }

    }

    public static void main(String[] args) {
        try (AutoCloseable resource1 = new MyResource("resource1", false, false, null);
                MyResource resource2 = new MyResource("resource2", false, true, null);
                AutoCloseable resource3 = new MyResource("resource3", false, false, resource2);
                AutoCloseable resource4 = new MyResource("resource4", true, false, null);) {
            System.out.println("main program");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("openResources: " + openResources);
    }
}

有以下输出:

resource1 is now open
resource2 is now open
resource3 is now open
resource1 was successfully closed
Exception when creating resource4
openResources: [resource2, resource3]

我希望在使用 try-with-resources 时所有资源都已正确关闭,并且我不会有任何泄漏。但是,在这里,资源 2 和 3 被泄露。为什么?我不能依赖那个语法吗?

【问题讨论】:

  • 这与 try-catch 的“可靠性”无关,而与您连接资源的方式以及它们抛出异常的事实有关。关闭resource3会在尝试关闭封装的resource2时抛出异常。关闭resource2会抛出异常。
  • 是的,您创建的示例确实存在如上所述的缺陷。

标签: java try-with-resources


【解决方案1】:

由于定义异常的方式,资源 2 和 3 被“泄露”。由于资源 4 在创建时抛出异常,所以创建的资源将被关闭。资源 3 会尝试关闭,但因为它实际上是在尝试关闭资源 2,所以资源 3 会抛出异常(注意:此异常被抑制,因为它是在服务 try 部分中的原始异常时抛出的)。关闭资源 2 也会导致抑制异常,使资源 1 成为唯一关闭的资源。

【讨论】:

    【解决方案2】:

    根据JAVA docs

    如果一个初始化资源的自动关闭突然完成 因为抛出值 V,以及所有其他自动关闭 初始化资源正常完成,然后 try-with-resources 由于值 V 的抛出,语句突然完成。

    所以,这是作为设计工作的。引入资源尝试的原因是为了减少开发人员的任务并自行处理资源处理。在可靠性方面,即使是旧的代码方式也会导致同样的资源泄漏。让我们尝试用下面的 JAVA 文档中提到的方式来翻译我们的代码:

    https://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.20.3.1

    让我们尝试通过重新修改您的代码并以旧的方式编写它来理解这一点,即在 try 中创建资源并在 finally 中关闭

    public class Main {
    
        static Set<String> openResources = new TreeSet<>();
    
        static class MyResource implements AutoCloseable {
    
            boolean close;
            MyResource encapsulatedResource;
            String name;
    
            MyResource(String id, boolean exceptionOnCreate, boolean exceptionOnClose, MyResource encapsulatedResource) {
                this.close = exceptionOnClose;
                this.encapsulatedResource = encapsulatedResource;
                this.name = id;
                if (exceptionOnCreate) {
                    throw new RuntimeException("Exception when creating " + id);
                }
                openResources.add(id);
                System.out.println(name + " is now open");
            }
    
            @Override
            public void close() {
                if (close) {
                    throw new RuntimeException("Exception when closing " + name);
                }
                if (encapsulatedResource != null) {
                    encapsulatedResource.close();
                }
                openResources.remove(name);
                System.out.println(name + " was successfully closed");
            }
    
        }
    
        public static void main(String[] args) {
            AutoCloseable resource1 = null, resource3 = null, resource4 = null;
            MyResource resource2 = null;
            try {
                resource1 = new MyResource("resource1", false, false, null);
                resource2 = new MyResource("resource2", false, true, null);
                resource3 = new MyResource("resource3", false, false, resource2);
                resource4 = new MyResource("resource4", true, false, null);
                System.out.println("main program");
            } catch (Exception e) {
                System.out.println(e.getMessage());
            } finally {
                try {
                    resource1.close();
                    resource2.close();
                    resource3.close();
                    resource4.close();
                } catch (Exception e) {
                    System.out.println(e.getMessage());
                }
            }
            System.out.println("openResources: " + openResources);
        }
    }
    

    如果你运行这个程序,你会得到以下输出:

    resource1 is now open
    resource2 is now open
    resource3 is now open
    Exception when creating resource4
    resource1 was successfully closed
    Exception when closing resource2
    openResources: [resource2, resource3]
    

    如您所见,资源 2 和资源 3 是泄露的资源 资源 2 的关闭导致异常进一步阻止关闭 资源 3.

    类似地,使用资源尝试,它期望在关闭时不会抛出异常,如果抛出,资源的关闭将在那个时间点终止。

    这并不意味着尝试使用资源不可靠或较旧 资源处理更好。这只是意味着可关闭 资源无法正常工作,应该关闭或至少关闭 应该已经处理了异常,以便剩余的资源关闭可以 继续。

    希望对您有所帮助。

    【讨论】:

    • 您已经发布了翻译,但您的代码不等效。
    • @SotiriosDelimanolis 感谢您的评论。我已经发布了 JAVA 文档所说的内容,但如果您觉得它不同,请分享上述代码的等效内容。
    • 除此之外,您还没有正确实现这一点如果 ResourceSpecification 声明了 n > 1 个资源,则 ResourceSpecification_tail 包含在中声明的第 2、3、...、第 n 个资源ResourceSpecification,以相同的顺序(并且 try-catch-finally 语句本身就是一个 try-with-resources 语句)。 每个close() 调用都包含在null 检查和它自己的try-catch 语句中.它在所有资源上调用close。在您的代码中,它会在第一次调用失败后停止。
    • @SotiriosDelimanolis 根据 JAVA 文档:如果一个初始化资源的自动关闭由于值 V 的抛出而突然完成,并且初始化资源的所有其他自动关闭正常完成,那么尝试-with-resources 语句由于抛出 V 值而突然完成。 这导致所有关闭都在一次尝试中捕获的情况
    • 不,它没有。它特别说明了 [...] 和所有其他自动关闭 [...]。例如,如果第一个失败,您的代码不会执行所有 其他关闭。那句话的意思是,如果只有一个失败,那么相应的异常就是从 try-with-resources 语句中抛出的异常。如果不止一个抛出异常,则应用整个被抑制的异常行为。
    最近更新 更多