首页 > 代码库 > 第7条:避免使用终结方法
第7条:避免使用终结方法
本条的意思是,让你尽量不要在你的类中覆盖finalize方法,然后在在里面写一些释放你的类中资源的语句。
至于为什么要避免覆盖并使用finalize呢,理由如下:
(1)finalize方法不能保证它能被及时的执行。
(2)finalize方法甚至都不会被执行。
(3)System.gc和System.runFinalization这两个方法只是能增加finalize方法被调用的几率。
(4)唯一能保证finalize方法被执行的方法有两个,System.runFinalizersOnExitRuntime.runFinalizersOnExit但是这两个方法已经被弃用。
(5)覆盖并使用终结方法会造成严重的性能损失。(在机器上,创建和销毁一个简单对象时间大约5.6ns,二增加一个终结方法使时间增加到了2400ns,慢了约430倍)。
那么,如果类中的资源确实需要被释放,我们应该如何操作呢?一般来说,需要释放的资源有线程或者文件还有涉及到本地的资源的对象。我们只需要提供一个显示的终止方法,用来释放资源,并要求这类的使用者在不再使用这个类的时候调用这个方法,并且在类中添加一个标志,来标记资源是否已经释放,如果已经被释放了,那这个类中的方法如果在被调用的话就抛出IllegalStateException异常,一个很好的例子就是InputStream和OutputStream。一般在调用我们自己定义的public修饰的终止方法的时候最好和try—finally一起使用,就像下面这样:
class MyObject{ private boolean isClosed = false; //public修饰的终止方法 public void close(){ //资源释放操作 ... isClosed = true; } } public static void main(String... args) { MyObject object = new MyObject(); try{ //在这里面使用object; ... } finally { //在这里面关闭object; object.close(); } }
至于什么时候使用终结方法才是比较合理的呢?当子类覆盖了超类的终结方法,但忘记手工调用超类的终结方法或有意不调用,那么超类的终结方法将永远不会被调用,要防范这样的粗心大意或恶意的子类,只时候我们可以使用下面方式
(1)用终结方法充当“安全网”
“安全网”的作用是当我们提供的public修饰的终结方法被在外部忘记调用的时候提供一种安全保障。我们可以看下FileInputStream
public void close() throws IOException { synchronized (closeLock) { if (closed) { return; } closed = true; } if (channel != null) { channel.close(); } fd.closeAll(new Closeable() { public void close() throws IOException { close0(); } }); } protected void finalize() throws IOException { if ((fd != null) && (fd != FileDescriptor.in)) { /* if fd is shared, the references in FileDescriptor * will ensure that finalizer is only called when * safe to do so. All references using the fd have * become unreachable. We can call close() */ close(); } }
可以看到FileInputStream还是有覆盖finalize方法的,而里面做的就是调用close方法,这是为了当对象持有者忘记调用close方法,在finalize方法中为它做调用close的事,这就是“安全网”的意思。
(2)终结方法的守卫者。把终结方法放在一个匿名的类,该匿名类的唯一用途就是终结它的外围实例。外围实例持有对终结方法守卫者的唯一实例,这意味着当外围实例是不可达时,这个终结方法守卫者也是不可达的了,垃圾回收器回收外围实例的同时也会回收终结方法守卫者的实例,而终结方法守卫者的finalize方法就把外围实例的资源释放掉,就好像是终结方法是外围实例的一个方法一样。来看看java.util.Timer的终结方法守卫者:
public void cancel() { synchronized(queue) { thread.newTasksMayBeScheduled = false; queue.clear(); queue.notify(); // In case queue was already empty. } } private final Object threadReaper = new Object() { protected void finalize() throws Throwable { synchronized(queue) { thread.newTasksMayBeScheduled = false; queue.notify(); // In case queue is empty. } } };
cancel方法是Timer提供的显式终止方法,threadReaper是一个私有变量,保证除了实例本身外,没有对它的引用,它覆盖finalize方法,实现与cancel方法一样的功能。我们在看个简单点的例子来加深下理解
class A { @SuppressWarnings("unused") //终结守卫者 private final Object finalizerGuardian = new Object() { @Override //终结守卫者的终结方法将被执行 protected void finalize() { System.out.println("A finalize by the finalizerGuardian"); } }; @Override //由于终结方法被子类覆盖,该终结方法并不会被执行 protected void finalize() { System.out.println("A finalize by the finalize method"); } public static void main(String[] args) throws Exception { B b = new B(); b = null; System.gc(); Thread.sleep(500); } } class B extends A { @Override public void finalize() { System.out.println("B finalize by the finalize method"); } }
运行的结果是:
B finalize by the finalize method
A finalize by the finalizerGuardian
最后,在使用终结方法的时候我们要注意什么?其实很简单,只需要注意确保super.finalize()方法一定会被执行。
确保它一定会被执行的方式有两种:
(1)使用try-finally(像上面的安全网一样);
(2)使用“终结方法守卫”
第7条:避免使用终结方法