zoukankan      html  css  js  c++  java
  • 第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)使用“终结方法守卫”

     

    作者:哀&RT
    出处:博客园哀&RT的技术博客--http://www.cnblogs.com/Tony-Anne/
    您的支持是对博主最大的鼓励,感谢您的认真阅读。
    本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    SQL Server 2008 Service Broker
    微软官网推Windows 7学习材料
    ASP.NET MVC Code and Slides for Best of Mix 09 Presentation
    DLINQ:SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM
    SQL Server 2008 Developer Training Kit
    TheBeerHouseASP.NET MVC范例
    SQL Server 2008 SP1
    LINQ: There is already an open DataReader associated with this Command which must be closed first
    Quartz.NET 1.0.1发布
    K2 Blackpearl的Outcomes Actions和Line Rule
  • 原文地址:https://www.cnblogs.com/Tony-Anne/p/6696258.html
Copyright © 2011-2022 走看看