第7条:避免使用终结方法
Posted remote
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第7条:避免使用终结方法相关的知识,希望对你有一定的参考价值。
第7条:避免使用终结方法
终结方法(finalizer)通常是不可预测的,也是很危险的,一般情况下是不必要的。使用终结方法会导致行为不稳定,降低性能以及可移植性问题。
C++程序员被告知“不要把终结方法当做C++中的析构器(destructors)的对应物”。在C++中,析构器是回收一个对象所占用资源的常规方法,是构造器所必需的对应物。在Java,当一个对象变得不可到达的时候,垃圾回收器会回收与该对象相关联的存储空间,并不需要程序员做专门的工作。C++的析构器也可以被用作回收其它的非内存资源。而在Java中,一般用try-finally块来完成类似的工作。
终结方法的缺点在于不能保证会及时地执行,从一个对象变得不可到达开始到它终结方法被执行,所花费的这段时间是任意长的,这就意味着,注重时间(time-critical)的任务不应该由终结方法来完成。及时地执行终结方法正是垃圾回收算法的一个主要功能,这种算法在不同的JVM上的执行时间是大相径庭的。
Java语言规范不仅不保证终结方法会被及时地执行,而且根本不保证它们会被执行。当一个程序终止的时候,某些已经无法访问的对象上的终结方法却根本没有被执行,这是完全有可能的。
结论:不应该依赖终结方法来更新重要的持久状态。例如,依赖终结方法来释放共享资源(比如数据库)上的永久锁,很容易让整个分布式系统垮掉。
当你不确定是否应该避免使用终结方法的时候,这里还有一种值得考虑的情形:如果未被捕获的异常在终结过程中被抛出来,那么这种异常可以被忽略,并且该对象的终结过程也会被终止。未被捕获的异常会使对象处于破坏的状态(a corrupt state),如果另一个线程企图用这种被破坏的对象,则可能发生任何不确定的行为。正常情况下,未被捕获的异常将会使线程终止,并打印出栈轨迹(Stack Trace),但是,如果异常发生在终结方法中,则不会如此,甚至连警告都不会打印出来。
还有一点:使用终结方法有一个非常严重的(Severe)性能损失。使用终结方法创建和销毁一个对象比使用普通方法创建和销毁对象慢大约400倍。
那么,如果类的对象中封装的资源(例如文件或者线程)的确需要终止,应该怎么做才能不编写终结方法呢?只需提供一个显示的终止方法,并要求该类的客户端在每一个实例不在有用的时候调用这个方法。值得一提的细节是,该实例必须记录下自己是否已经被终结了:显示的终止方法必须在一个私有域中记录下“该对象已经不在有效”。如果这些方法是在对象已经终止之后被调用,其它的方法就必须检查这个域,并抛出IllegalStateException异常。显示终结方法的典型例子是InputStream、OutputStream和java.sql.Connection上的close方法。
显示终结方法经常与try-finally结构结合起来使用,以确保及时终止。在finally子句内部用显示的终止方法,可以确保即使在使用对象的时候有异常抛出,该终止方法也会执行:
1 Foo foo = new Foo(...); 2 try{ 3 //Do what must be done with foo 4 ... 5 }finally{ 6 foo.terminate(); //Explicit termination method 7 }
使用终结方法的好处,它们有两种合法用途:
第一种用途是,当对象的所有者忘记调用前面建议的显示终止方法的时,终结方法可以充当“安全网(safety net)”。迟一点释放关键资源总比永远不释放要好。但是如果终结方法发现资源还未被终止,则应该在日志中记录一条警告,因为这是客户端的一个BUG,应当被修复。
第二种合理用途与对象的本地对等体(native peer)有关。
以上是关于第7条:避免使用终结方法的主要内容,如果未能解决你的问题,请参考以下文章
《Effective Java 中文版 第2版》学习笔记 第7条:避免使用终结方法