Java中为什么反对使用Thread.stop, Thread.suspend, Thread.resume and Runtime.runFinalizersOnExit?

来源:互联网 发布:ico图标制作软件 编辑:程序博客网 时间:2024/04/28 20:39

转载请注明来源-作者@loongshawn:http://blog.csdn.net/loongshawn/article/details/53034176

原文:《Why Are Thread.stop, Thread.suspend, Thread.resume and Runtime.runFinalizersOnExit Deprecated?》
翻译:loongshawn
日期:2016-11-06
备注:欢迎勘误,同时欢迎推荐优秀外文

为什么反对使用Thread.stop?

因为它本身是不安全的。停掉一个线程将会导致所有已锁的监听器被解锁(监听器解锁状态将会随着ThreadDeath异常一直传播到堆中)。任何之前被这些监听器保护的对象将会变为不可预知的状态,其他线程对这些对象的可视状态也会是不可预知。这些对象就认为被损坏了。当线程操作损害的对象,将会导致任意的行为。这些行为有可能很细微或很难被探测,或者很明显。不像其他unchecked异常,ThreadDeath默默的杀死线程,用户的程序可能破坏后得不到任何警告信息。这种破坏将会在实际损害发生后的任何时候产生,可能是未来的几个小时或者几天。

我可否捕获ThreadDeath异常,同时修复受损对象?

理论上是可能的,但是写修改多线程代码的任务将会极其复杂,这项任务基于以下2个原因几乎是不可能的:

  • 1.线程抛出ThreadDeath异常几乎在任务地方,所有的同步方法及块需要极其仔细的研究。
  • 2.线程在清理第一次异常时将会抛出2次ThreadDeath异常(在 catch or finally分句中)。清理动作将会重复直到成功,保证这个完成的代码将会非常复杂。

所以,这种想法是不实际的。

Thread.stop(Throwable)这个怎么样?

补充上面的问题,这个方法将会被用来产生他们的目标线程并不打算捕获的异常(包括线程不可能抛出不是这个方法的受检查异常)。例如:下面这个方法与Java throw操作一致,但是绕过了编译器试图确保调用方法已经申明了所有可能抛出的异常这个动作。

    static void sneakyThrow(Throwable t) {        Thread.currentThread().stop(t);    }

用什么方法替代Thread.stop?

许多使用stop方法应该被替换为简单的修改某些代表线程运行状态的变量。目标线程需要时不时的去检查这个变量的状态,如果检查到变量是停止运行,则能够从运行方法中返回出来(这种方式也是Java指导手册中推荐的方法)。为了确保停止请求及时的交流,这个状态变量应该在前面加上volatile(或者为一个同步变量)。

例如,假设你的应用程序包含了start、stop、run方法:

    private Thread blinker;    public void start() {        blinker = new Thread(this);        blinker.start();    }    public void stop() {        blinker.stop();  // UNSAFE!    }    public void run() {        Thread thisThread = Thread.currentThread();        while (true) {            try {                thisThread.sleep(interval);            } catch (InterruptedException e){            }            repaint();        }    }

你可以通过修改stop、run方法来避免使用 Thread.stop:

    private volatile Thread blinker;    public void stop() {        blinker = null;    }    public void run() {        Thread thisThread = Thread.currentThread();        while (blinker == thisThread) {            try {                thisThread.sleep(interval);            } catch (InterruptedException e){            }            repaint();        }    }

怎样停掉一个等待了很长时间的线程(比如为了输入)

这就是Thread.interrupt方法所用之处。也可以使用上文那种基于信号机制,但是状态的改变(blinker = null,上例中)可以跟随一个调用Thread.interrupt来打断等待。

    public void stop() {        Thread moribund = waiter;        waiter = null;        moribund.interrupt();    }

在捕获了中断异常同时又不打算立即确认该异常这种情况中这种方式是至关重要的。我们称之为“再次确认”而不是“再次抛出”,是因为它并非总是会抛出异常。如果方法捕获到了并没有申明来捕获受检查异常时,它应该通过下列方式自我中断。

    Thread.currentThread().interrupt();

这样能够确保线程尽快提起InterruptedException。

线程对Thread.interrupt没有响应将怎么办?

在某些情况下,你可以利用应用更具体的方式。例如,如果一个线程在一个已知的socket上等待时,你可以关闭socket从而使得线程立即返回。不幸的是,并没有任何一种通用的方式。必须明确一点的事,任何情况下一个等待线程对Thread.interrupt没有响应时,对Thread.stop同样不会有响应。这种情况包括对thread.stop和 thread.interrupt不工作时的故意拒绝服务袭击、IO操作。

为什么Thread.suspend和Thread.resume方法也不赞成?

Thread.suspend很容易死锁。如果目标线程挂起来,他将给监听器上锁用以保护重要的系统资源,其他线程将不能访问该资源直到目标线程恢复工作。如果线程在恢复一个企图给监听器加锁的线程前调用了resume方法,则导致死锁。这种死锁称之为冰冻过程。

用什么方法代替Thread.suspend和 Thread.resume?

与操作Thread.stop方式一样,谨慎的做法是给目标线程加一个标记,这个标记表面所需的状态(active or suspended),当这个标记的状态变为悬挂时,线程将会等候使用Object.wait。当线程恢复时,线程通知使用Object.notify。

例如,假设你的应用包括下列mousePressed事件捕获,通过blinker来切换状态:

    private boolean threadSuspended;    Public void mousePressed(MouseEvent e) {        e.consume();        if (threadSuspended)            blinker.resume();        else            blinker.suspend();  // DEADLOCK-PRONE!        threadSuspended = !threadSuspended;    }

你可以通过改写mousePressed方法,从而避免使用Thread.suspend 和 Thread.resume :

    public synchronized void mousePressed(MouseEvent e) {        e.consume();        threadSuspended = !threadSuspended;        if (!threadSuspended)            notify();    }

同时给“run loop”中新增如下代码:

    synchronized(this) {        while (threadSuspended)            wait();    }

wait方法会抛出InterruptedException,因此wait需要加到try…catch语句中,把sleep也放到同一个语句中是好的。检查应该在sleep之后(而不是之前),因此在线程状态为“resumed”时窗口将会立即唤起。run方法如下:

    public void run() {        while (true) {            try {                Thread.currentThread().sleep(interval);                synchronized(this) {                    while (threadSuspended)                        wait();                }            } catch (InterruptedException e){            }            repaint();        }    }

需要注意的是mousePressed方法中的notify及run方法中的wait都是内部同步块,这是语言级别的要求,同时要确保notify与wait正确的序列化。在实际中,这种排除竞争条件将会导致suspended线程丢失notify同时保持无期限的挂起。

随着Java平台的成熟,同步的代价在降低,但是永远不会没有。一种简单的方式可以用来给我们已经为每次迭代添加的“run loop”移除同步,同步代码块只在真正挂起的代码块中添加:

    if (threadSuspended) {         synchronized(this) {             while (threadSuspended)                 wait();        }    }

在这种缺省的同步中,threadSuspended需要加一个volatile确保及时的获知suspend请求:

    private boolean volatile threadSuspended;    public void run() {        while (true) {            try {                Thread.currentThread().sleep(interval);                if (threadSuspended) {                    synchronized(this) {                        while (threadSuspended)                            wait();                    }                }            } catch (InterruptedException e){            }            repaint();        }    }

未完待续。。。

0 0