RAII in Java

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

资源获取即初始化(RAII)是由Bjarne Stroustrup在C++中引入的一种设计理念,用于异常安全的资源管理。由于Java有垃圾回收机制,因此没有这个特性,但我们可以使用try-with-resources来实现类似的功能。

RAII解决的问题很明显,看一下这段代码(我相信你知道Java中的Semaphore是什么以及它是如何工作的):

代码相当原始,没有任何实用功能,但您很可能能理解:方法print()如果被多个并行线程调用,只允许其中五个并行打印。有时,如果x大于1000,它将不允许其中一些线程打印,并会抛出异常。

这段代码的问题在于——资源泄漏。每次print()调用中,如果x大于1000,将会从信号量中获取一个许可证,并且不会归还。在五次带有异常的调用中,信号量将为空,而其他线程将无法打印任何内容。

解决方案是什么?就是这样:

在我们抛出异常之前,我们必须释放许可。

然而,还有另一个问题出现了:代码重复。我们在两个地方释放许可。如果我们添加更多的throw指令,也必须添加更多的sem.release()调用。

在C++中引入了一个非常优雅的解决方案,称为RAII。这是在Java中的实现方式。

看一下方法 Foo.print() 中的代码是多么美丽。我们只需要创建一个 Permit 类的实例,它立即在信号量中获取一个新的许可。然后我们退出方法 print(),无论是通过异常还是正常方式,方法 Permit.finalize() 会释放该许可。

优雅,不是吗?是的,确实优雅,但在Java中不起作用。

它不起作用的原因是,与C++不同,Java不会在对象的可见范围关闭时销毁对象。当我们退出方法 print() 时,Permit 类的对象不会被销毁。它最终会被销毁,但我们不知道具体是何时。很可能在所有许可都被获取并被阻塞之后才会被销毁。

在Java中也有一种解决方案。它不像C++中的方案那样优雅,但它确实有效。这是它:

请注意try块和类Permit现在实现的Closeable接口。当try块退出时,对象p将被“关闭”。它可以在结尾处退出,也可以通过returnthrow语句退出。无论哪种情况,都将调用Permit.close():这就是Java中try-with-resources的工作原理。

我引入了acquire()方法,并将sem.acquire()移出了Permit构造函数,因为我认为构造函数必须是无代码的。

总之,当你处理可能泄露的资源时,RAII是一种完美的设计方法。尽管Java没有内置的支持,但我们可以通过try-with-resourcesCloseable来实现它。

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-11-28 at 14:35

sixnines availability badge   GitHub stars