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
将被“关闭”。它可以在结尾处退出,也可以通过return
或throw
语句退出。无论哪种情况,都将调用Permit.close()
:这就是Java中try-with-resources的工作原理。
我引入了acquire()
方法,并将sem.acquire()
移出了Permit
构造函数,因为我认为构造函数必须是无代码的。
总之,当你处理可能泄露的资源时,RAII是一种完美的设计方法。尽管Java没有内置的支持,但我们可以通过try-with-resources和Closeable
来实现它。
Translated by ChatGPT gpt-3.5-turbo/42 on 2023-11-28 at 14:35