首页 > 代码库 > 第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条:避免使用终结方法