第7条:避免使用终结方法

  本条的意思是,让你尽可能不要在你的类中覆盖finalize方法,而后在在里面写一些释放你的类中资源的语句。java

  至于为何要避免覆盖并使用finalize呢,理由以下:安全

    (1)finalize方法不能保证它能被及时的执行。
    (2)finalize方法甚至都不会被执行。
    (3)System.gc和System.runFinalization这两个方法只是能增长finalize方法被调用的概率。
    (4)惟一能保证finalize方法被执行的方法有两个,System.runFinalizersOnExitRuntime.runFinalizersOnExit可是这两个方法已经被弃用。
    (5)覆盖并使用终结方法会形成严重的性能损失。(在机器上,建立和销毁一个简单对象时间大约5.6ns,二增长一个终结方法使时间增长到了2400ns,慢了约430倍)。ide

  那么,若是类中的资源确实须要被释放,咱们应该如何操做呢?通常来讲,须要释放的资源有线程或者文件还有涉及到本地的资源的对象。咱们只须要提供一个显示的终止方法,用来释放资源,并要求这类的使用者在再也不使用这个类的时候调用这个方法,而且在类中添加一个标志,来标记资源是否已经释放,若是已经被释放了,那这个类中的方法若是在被调用的话就抛出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();
    }
}

  至于何时使用终结方法才是比较合理的呢?当子类覆盖了超类的终结方法,但忘记手工调用超类的终结方法或有意不调用,那么超类的终结方法将永远不会被调用,要防范这样的粗枝大叶或恶意的子类,只时候咱们可使用下面方式spa

   (1)用终结方法充当“安全网”线程

    “安全网”的做用是当咱们提供的public修饰的终结方法被在外部忘记调用的时候提供一种安全保障。咱们能够看下FileInputStreamcode

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的终结方法守卫者:blog

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方法同样的功能。咱们在看个简单点的例子来加深下理解ip

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)使用“终结方法守卫”

相关文章
相关标签/搜索