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

Posted 没有梦想的小灰灰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第7条:避免使用终结方法相关的知识,希望对你有一定的参考价值。

终结方法(finalize)通常是不可预测的,它不是C++中析构器(destructors)的对应物,Java中一旦对象变的不可达,垃圾回收器会自动回收与该对象相关的存储空间,不需要程序员做专门的工作。C++析构器也可以用来回收其他非内存资源,在Java中,通常用try-finally来完成。

 

终结方法的缺点:

1.不能保证被及时执行,注重时间的任务不应该由终结方法完成,比如用终结方法关闭已经打开的文件,因为文件描述符是有限的资源,如果大量文件保留在打开状态,当程序不能再打开文件,可能会运行失败,还有,如果是以写方式打开文件,这时候文件就不应该再被读或者写了,只有等待被写的文件关闭,表示写完,才可继续进行另外的操作,如果终结方法迟迟不执行,那么整个文件在已经写完,但还没关闭的情况下,无法被访问,大大降低性能。

2.即使地执行终结方法是回收算法的一个主要功能,由于不同的JVM的回收算法实现大相庭径,使用终结方法可能会丧失平台无关性。

3.Java语言规范不保证终结方法会被执行,当一个程序终止,某些无法访问的对象的终结方法没有被执行是完全有可能的,比如,依赖终结方法来释放数据库上的永久锁,可能会导致锁永远不会被释放。

 

如果类的对象中封装的资源确实需要终止,应该怎么做。

提供一个显式的终结方法。意思是,我们不去覆盖finalize方法,而是自己编写一个终止资源的方法。比如,java.io.FileInputStream的close方法。当使用完这个FileInputStream对象时,显式调用close() 来回收资源。

 

终结方法的好处:

1.当对象的所有者忘记调用显式终结方法时,终结方法可以充当"安全网"。

来看看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方法一样的功能。

以上是关于第7条:避免使用终结方法的主要内容,如果未能解决你的问题,请参考以下文章

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

《Effective Java 中文版 第2版》学习笔记 第7条:避免使用终结方法

第8项:避免使用终结方法和清空方法

需要一种有效的方法来避免使用 Laravel 5 重复代码片段

Effective Java2读书笔记-创建和销毁对象

Java:Effective java学习笔记之 避免使用终结方法