`

[转]为什么 Thread.stop和Thread.suspend等被废弃了?

阅读更多

 

原文 Why Are Thread.stop, Thread.suspend,Thread.resume and Runtime.runFinalizersOnExit Deprecated?

为什么 Thread.stop 被废弃了?

因为其天生是不安全的。停止一个线程会导致其解锁其上被锁定的所有监视器(监视器以在栈顶产生ThreadDeath异常的方式被解锁)。如果之前被这些监视器保护的任何对象处于不一致状态,其它线程看到的这些对象就会处于不一致状态。这种对象被称为受损的 (damaged)。当线程在受损的对象上进行操作时,会导致任意行为。这种行为可能微妙且难以检测,也可能会比较明显。不像其他未受检的(unchecked)异常,ThreadDeath 悄无声息的杀死及其他线程。因此,用户得不到程序可能会崩溃的警告。崩溃会在真正破坏发生后的任意时刻显现,甚至在数小时或数天之后。


难道我不能仅捕获 ThreadDeath 异常来修正受损对象吗?

理论上,也许可以,但书写正确的多线程代码的任务将极其 复杂。由于两方面的原因,这一任务的将几乎不可能完成:

  1.  
    1. 线程可以在几乎任何地方抛出 ThreadDeath 异常。由于这一点,所有的同步方法和(代码)块将必须被考虑得事无巨细。

    2. 线程在清理第一个 ThreadDeath 异常的时候(在 catch  finally 语句中),可能会抛出第二个。清理工作将不得不重复直到到其成功。保障这一点的代码将会很复杂。


Thread.stop(Throwable) 会有什么问题?
	除了上述所有问题外,此方法还可能产生其目标线程不准备处理的异常(包括若非为实现此方法,线程不太可能抛出的受检异常)。例如,下面的方法行为上等同于Java的throw操作,但是绕开了编译器的努力,即保证要调用的方法已经声明了其所有可能抛出的异常:
    static void sneakyThrow(Throwable t) {
        Thread.currentThread().stop(t);
    }
我应该用什么来取代 Thread.stop 

大多数stop的使用,应当被替换为简单修改某些变量来指示其目标线程将停止运行的代码。目标线程应当有规律的检查这些变量。并且,如果这些变量指示其将停止运行,目标线程应当以某种有序的方式从它的run方法返回(这正是Java Tutorial一贯建议的方式)。为了确保停止请求的及时传达,变量必须是 volatile 的(或者变量的访问被同步)。

例如,假设你的 applet 包含了 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();
        }
    }

为了避免使用 Thread.stop ,你可以把applet的stop和run方法替换成:

    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();
    }

为了让这种技术起作用,关键在于,对于任何捕获了中断异常但不准备立即处理的方法,应重新断言异常。我们说重新断言 (reasserts)而不是重新抛出 (rethorws),因为要重新抛出异常并非总是可行。如果捕获InterruptedException 的方法没有被声明为抛出这种(受检)异常,那么它应该采用下述咒语来“重新中断自己”:

    Thread.currentThread().interrupt();
如果线程没有响应 Thread.interrupt 会怎么样?

在某些情况下,你可以采取特定于应用的技巧。例如,如果线程在一个已知的socket上等待,你可以关闭这个socket,来促使线程立即返回。不幸的是,确实没有放之四海皆可工作的通用技术。应当注意,对于等待线程不响应 Thread.interrupt 的所有情况 ,它也不会响应 Thread.stop  这些案例包括有意的拒绝服务攻击,以及thread.stop和thread.interrupt不能正常工作的I/O操作。


为什么 Thread.suspend 和 Thread.resume 被废弃了?

Thread.suspend 天生容易引起死锁。如果目标线程挂起时在保护系统关键资源的监视器上持有锁,那么其他线程在目标线程恢复之前都无法访问这个资源。如果要恢复目标线程的线程在调用 resume 之前试图锁定这个监视器,死锁就发生了。这种死锁一般自身表现为“冻结( frozen )”进程。


我应该用什么来取代 Thread.suspend 和 Thread.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();  // DEADLOCK-PRONE!

        threadSuspended = !threadSuspended;
    }

要避免使用 Thread.suspend  Thread.resume  你可以把上述事件句柄替换为:

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

        threadSuspended = !threadSuspended;

        if (!threadSuspended)
            notify();
    }

并把下述代码增加到“运行循环”中:

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

wait方法抛出 InterruptedException ,因此他必须在一个“ try ... catch” 语句中。使用 sleep 方法时,也可以将其放入同样的语句中。检查应该在 sleep 方法后(而不是先于),以便当线程恢复的时候窗口被立即重绘。修改后的 run 方法如下:

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

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

注意,mousePressed 方法中的 Notify和run方法中的wait都是在 synchronized 语句块中的。这是语言的要求,也确保了 wait  notify 被正确串行化执行。从实际效果来看,这消除了竞争条件,避免了不确定的“挂起”线程丢失 notify 消息而仍保持挂起。

虽然随着平台的成熟Java的同步开销正在减少,但其永远都不会是免费的。有一个简单的技巧,可以用于移除我们加入到“运行循环”每次迭代中的同步。加入的同步块被替换为稍微有点复杂的代码片段,只有当线程真正被挂起的时候后才会进入同步块:

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

由于缺少显式同步, threadSuspended 必须被指定为 volatile 来保证挂起请求被迅速传递。

修改后的 run 方法如下:

    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();
        }
    }

我可以结合两种技术来产生可以安全“停止”或“挂起”的线程吗?

是的,这这相当直观。有一个不易察觉的地方,那就是当目标线程可能已经挂起的时候,另外一个线程试图停止它。如果stop方法只是将状态变量(blinker)设置为null,目标线程将仍然处于挂起状态(等待监视器),而不是它所应该的优雅退出。如果applet被重启,多个线程可能会同时结束在monitor上等待,从而导致奇怪的行为。

为了矫正这种状况,stop方法必须保证挂起的目标线程迅速恢复。一旦目标线程恢复,它必须立即认识到它已经被停止了,并且优雅的退出。这里是修改过的run和stop方法:

    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 了,但是他仍然需要被同步。这确保了目标线程不会因竞争条件而丢失中断。


关于 Thread.destroy 如何呢?

Thread.destroy 从未被实现。如果它被实现了,它将和 Thread.suspend 一样易于死锁(事实上,它大致上等同于没有后续 Thread.resume  Thread.suspend )。我们现在既没有实现,也没有废除它(防止将来它被实现)。虽然它确实易于发生死锁,有人争论过,在有些情况下程序可能愿意冒死锁的险而不是直接退出。


为什么 Runtime.runFinalizersOnExit 被废弃了?

由于其天生不安全。它可能导致终结器(finallizers)被在活动对象上被调用,而其他线程正在并发操作这些对象,导致奇怪的行为或死锁。然而,如果正在被终结对象的类被编码为“防御”这种调用,这个问题可以避免。大多数程序员都不会 阻止它。它们假设当终结器被调用的时候对象对象已经死亡。

而且,这个调用不是“线程安全”的,因为它设置了一个VM全局标志。这迫使每个带有终结器的类防御活动对象的终结!

 

原文:http://blog.csdn.net/dlite/article/details/4212915

分享到:
评论

相关推荐

    Java多线程-Thread类的常用结构及线程优先级

    - `suspend()` 和 `resume()`:挂起和恢复线程,由于可能导致死锁,这两个方法也被废弃。 二、线程的优先级 1. 线程调度策略 Java中的线程调度有两种策略:抢占式调度和合作式调度。Java采用抢占式调度,优先级...

    同时实现了时钟以及秒表的功能

    - 在实际开发中应避免使用`Thread`类中的`suspend()`和`stop()`方法,因为它们已被废弃。可以使用标志变量控制线程的运行状态。 - 应用程序中可能还需要添加异常处理机制来提高稳定性。 - 考虑到GUI应用程序的特点,...

    Java Thread Programming

    2. **线程的暂停、恢复与终止**:通过`stop()`(已废弃)、`suspend()`(已废弃)、`resume()`(已废弃)、`interrupt()`、`join()`和`yield()`等方法对线程进行控制。 3. **线程优先级**:`setPriority()`方法可以...

    Java多线程的用法详解

    - `stop()`:强制停止线程(已废弃,不推荐使用,可能导致数据不一致)。 - `interrupt()`:中断线程,设置线程的中断标志位。 - `isAlive()`:检查线程是否仍然处于活动状态。 - `isInterrupted()`:检查线程是否被...

    JAVA100例之实例66 实现对线程的控制,中断、挂起、恢复、停止

    在Java中,`Thread.suspend()`和`Thread.resume()`方法已经被废弃,因为它们可能导致死锁。现在,推荐使用`wait()`和`notify()`或`notifyAll()`来实现线程的挂起和恢复。当一个线程调用`wait()`时,它会释放持有的...

    Java程序开发教学课件:10-线程.ppt

    Java 8.0引入了Lambda表达式,这为线程的创建和管理提供了更简洁的语法。 线程的状态主要有五种: 1. **新建(New)**:当使用`new Thread()`创建了一个线程对象时,线程处于新建状态。 2. **可运行(Runnable)**...

    Java多线程机制(示例)

    - `public final void suspend()`:已废弃,用于挂起线程。 - `public final void resume()`:已废弃,用于恢复被挂起的线程。 - `public final synchronized void join(long millis) throws ...

    Java入门笔记6_线程

    此外,`Thread`类提供了其他方法,如`stop()`(不推荐使用,因为可能导致数据不一致),`suspend()`(已废弃,因为它会导致死锁),`resume()`(与`suspend()`配合使用,但现在也废弃了)等。 示例中展示了`...

    文章分类:Java编程,java面试题3

    这些知识点包括:`Thread`类与`Runnable`接口的区别、`synchronized`关键字的作用、`stop()`与`suspend()`方法为何不推荐使用、`sleep()`与`wait()`的区别、同步锁与非同步锁的不同、线程的状态及其转换等。...

    java 面试题

    为什么? 可以。在Java中,`char`类型使用的是Unicode编码,它能够表示所有常见的字符集,包括中文汉字。每个`char`变量占用2个字节的空间,足以容纳一个汉字。 #### 8\. 用最有效率的方法算出2乘以8等于几? 最...

    Java面试宝典2012版.pdf

    为什么? `char`类型可以存储一个中文汉字,因为Java使用Unicode编码,一个`char`类型占用2个字节,足够存储一个Unicode字符,包括中文汉字。 ### 8. 用最有效率的方法算出2乘以8等于几? 最有效率的方法是使用位...

    java课件, 包含多线程

    然而,此方法已被废弃,推荐使用其他方式如设置线程的中断状态来优雅地停止线程,以避免可能的数据不一致和死锁问题。 ### 线程的生命周期与调度 线程的生命期分为以下几个阶段: 1. **新建状态(New)**:线程...

    Java面试题几道基础题总结

    - **`stop()` 方法**:该方法已被标记为废弃,因为它可能会导致数据不一致和资源泄露等问题。当调用线程的 `stop()` 方法时,线程会立即停止运行,这可能导致线程正在执行的关键操作被中断,从而引发数据不一致问题...

    java面试宝典

    - `stop()` 和 `suspend()` 方法容易引发死锁问题,已被废弃。 **47. sleep() 和 wait() 有什么区别?** - `sleep()` 方法使当前线程暂停一段时间,然后自动恢复。 - `wait()` 方法使当前线程等待直到被唤醒或超时...

    Java线程的简单例子

    启动线程只需调用start()方法,而停止线程则相对复杂,因为直接的stop()方法已废弃,为防止资源泄露,通常通过共享变量或者中断标志来通知线程停止。线程的暂停通常使用suspend()和resume(),但这些方法存在死锁风险...

    面试题大全

    为什么? `char`类型可以存储一个中文汉字,因为Java中的`char`类型使用的是Unicode编码,每个`char`占用2个字节,足以容纳包括中文在内的所有字符。 #### 8. 用最有效率的方法算出2乘以8等于几? 最有效率的方法是...

    Java面试宝典

    为什么?** 可以,因为 Java 使用 Unicode 字符集,`char` 类型占用 2 个字节,足以存储一个中文汉字。 **8. 用最有效率的方法算出 2 乘以 8 等于几?** 使用位运算符 `左移一位相当于乘以 2,因此 `2 相当于 `2 * ...

    线程暂停、开始、结束

    在Java中,`Thread.stop()`方法已废弃,因为其不安全。推荐的方式是让线程完成其工作,或者通过共享变量让线程自行结束。在C#中,`Thread.Abort()`也是不推荐的,最好通过设置标志或返回值让线程优雅地结束。 6. **...

    Java Thread多线程详解及用法解析

    - **resume()** 和 **suspend()**:这两个方法已被废弃,不建议使用,因为它们可能导致死锁。 - **sleep(long millis, int nanos)**:使当前线程暂停指定的毫秒数加纳秒数,进入等待状态。 - **stop()**:停止线程,...

Global site tag (gtag.js) - Google Analytics