第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)使用「終結方法守衛」

相關文章
相關標籤/搜索