为什么不鼓励使用 Thread.stop、Thread.suspend和 Thread.resume及解决方法

http://hi.baidu.com/%D7%CF%C9%AB%B5%C4%C6%D6%B9%AB%D3%A2/blog/item/6502e638319a2f21b8998fdf.html


为什么不鼓励使用 Thread.stop

因为它本质上就是不安全的。停止线程会导致解锁它已锁定的所有监视程序(ThreadDeath 异常传播到栈上后即解锁监视程序)。如果这些监视程序前面保护的任何对象处于不一致状态,则其它线程即可能将这些对象视为处于不一致状态。我们将这种对象称为损坏的对象。当线程操作损坏的对象时,可能会产生任何意外的行为。这种行为可能很难觉察,也可能非常明显。与其它未检查的异常不同,ThreadDeath 将悄悄杀掉线程;这样,用户就不会得到其程序已被破坏的警告。这种破坏可能会在实际损坏发生之后的任何时间显示出来,甚至可能会在数小时或数天之后。


难道不能捕获 ThreadDeath 异常并修复被损坏的对象?

从理论上是可行的,但编写正确的多线程代码的任务将会相当复杂。该任务几乎是无法完成的,原因如下:

  1. 线程可能会在几乎任何地方抛出 ThreadDeath 异常。所以必须在此基础上对所有的同步方法和块进行研究。
  2. 线程可能会在清除第一个 ThreadDeath 异常(在 catchfinally 子句中)时抛出第二个异常。 因此必须重复清除直到它成功完成。实现这一点的代码将相当复杂。

总之,上述意图是不切实际的。


对于 Thread.stop(Throwable) 又该怎样办?

除了上述所有问题之外,该方法还可能用来产生其目标线程尚不能处理的异常(包括离开该方法线程几乎不可能产生的已检查异常)。例如,下列方法的行为等同于 Java 的 throw 操作,但是绕过了编译器的下列尝试:即保证调用方法已经声明了它可能抛出的所有已检查异常:

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

应该用什么来代替 Thread.stop

大多数使用 stop 的情况都应该用简单修改一些变量以指示目标线程应停止运行的代码所代替。目标线程应该定期检查该变量,并在该变量指示需要它停止运行时以一种合理的方法从其 run 方法中返回(这是 JavaSoft 教程始终推荐的方法)。要确保停止请求的即时通讯,该变量必须是 volatile(迅变)的(或对该变量的访问必须是同步的)。

例如,假定 applet 包含下列 startstoprun 方法:

private Thread blinker;

    public void start() {
        blinker = new Thread(this);
        blinker.start();
    }

    public void stop() {
        blinker.stop();  // 不安全!
    }

    public void run() {
        Thread thisThread = Thread.currentThread();
        while (true) {
            try {
                thisThread.sleep(interval);
            } catch (InterruptedException e){
            }
            repaint();
        }
    }

则可以通过用下列内容替代 applet 的 stoprun 方法来避免使用 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();
    }

要使用这种技术,关键是任何捕获中断异常并无法立即处理它的方法要重新声明该异常。之所以称为重新声明而不是重抛出,是因为该异常不是总可以重新抛出的。如果捕获 InterruptedException 异常的方法没有声明抛出该(已检查的)异常,则它应该用下列代码“重新中断自己”:

Thread.currentThread().interrupt();

这可以确保 Thread 尽快重新产生 InterruptedException 异常。


如果线程不响应 Thread.interrupt 该怎么办?

有些情况下,可以使用应用程序特定技巧。例如,如果线程在等待已知套接字,则可以关闭该套接字以使线程立即返回。不幸地是,目前尚没有通用的技术。应该说明,对于所有等待线程不响应 Thread.interrupt 的情况,它也不会响应 Thread.stop 这种情况包括故意的拒绝服务攻击和 thread.stop 与 thread.interrupt 不能正常工作的 I/O 操作。


为什么不鼓励使用 Thread.suspendThread.resume

Thread.suspend 从本质上就是易于死锁的。如果目标线程锁定在一个监视程序上,从而在关键系统资源挂起时保护资源,则在目标线程恢复前将没有线程能访问该资源。如果试图恢复目标线程的线程在调用 resume 之前试图锁定该监视程序,即出现死锁。这种死锁通常将自己显示为“冻结”进程。


应该使用什么来代替 Thread.suspendThread.resume

Thread.stop 一样,最谨慎的方法是让“目标”线程轮询一个指示所需线程状态(活动或挂起)的变量。当需要挂起状态时,线程将使用 Object.wait 进行等待。当线程恢复时,将使用 Object.notify 通知目标线程。

例如,假定 applet 包含下列 mousePressed 事件处理程序,它将触发名为 blinker 的线程的状态:

private boolean threadSuspended;

    Public void mousePressed(MouseEvent e) {
        e.consume();

        if (threadSuspended)
            blinker.resume();
        else
            blinker.suspend();  // 易于死锁!

        threadSuspended = !threadSuspended;
    }

用下列代码替代上述事件处理程序,即可避免使用 Thread.suspendThread.resume

public synchronized void mousePressed(MouseEvent e) {
        e.consume();

        threadSuspended = !threadSuspended;

        if (!threadSuspended)
            notify();
    }

然后将下列代码添加到“run loop”中:

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

wait

public void run() {
        while (true) {
            try {
                Thread.currentThread().sleep(interval);

                synchronized(this) {
                    while (threadSuspended)
                        wait();
                }
            } catch (InterruptedException e){
            }
            repaint();
        }
    }

注意:mousePressed 方法中的 notifyrun 方法中的 wait 位于 synchronized 块内部。这是语言所要求的,可确保 waitnotify 的序列化。从实际应用来说,这消除了导致“挂起”线程丢失 notify 并无限期地保持挂起状态的竞争情况。

尽管 Java 中同步的费用随着平台的成熟不断下降,但它永远不会是免费的。用户可以使用一种简易技巧来删除添加在每一次“run loop”中的同步。前面添加的同步块被一段稍复杂的代码所替代(这段代码只有在实际挂起时才进入同步块):

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

最终的 run 方法为:

public void run() {
        while (true) {
            try {
                Thread.currentThread().sleep(interval);

                if (threadSuspended) {
                    synchronized(this) {
                        while (threadSuspended)
                            wait();
                    }
                }
            } catch (InterruptedException e){
            }
            repaint();
        }
    }

在没有提供显式同步前,threadSuspended 必须为 volatile,以确保挂起请求的快速通讯。


是否可以利用这两种技术产生能安全“停止”或“挂起”的线程?

是,这一点显而易见。但要注意的一个细节是:目标线程可能在另一个线程试图停止它时就已经挂起。如果 stop 方法仅将状态变量 (blinker) 设置为 null,则目标线程将仍然挂起(在监视程序上等待),而不是象它所应该的那样悄悄退出。如果该 applet 被重新启动,则最终可能会出现多个线程在监视程序上等待,从而导致错误行为。

为了解决该问题,stop 方法必须确保目标线程能在挂起时立即恢复。一旦目标线程恢复后,它必须立即识别出自己已被停止并悄悄退出。下面是最终的 runstop 方法:

public void run() {
        Thread thisThread = Thread.currentThread();
        while (blinker == thisThread) {
            try {
                thisThread.sleep(interval);

                synchronized(this) {
                    while (threadSuspended && blinker==thisThread)
                        wait();
                }
            } catch (InterruptedException e){
            }
            repaint();
        }
    }

    public synchronized void stop() {
        blinker = null;
        notify();
    }

如果 stop 方法调用 Thread.interrupt(如前所述),则它不必调用 notify,但仍必须实现同步。这可以确保目标线程不会因为偶然情况错过中断。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值