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

时间:2023-11-21 18:21:50

  Thread.stop, Thread.suspend, Thread.resume被标记为废弃的方法。在查看JDK的文档时,提到了下面的参考文章,先是英文版,接着是中文翻译。

  

Why is Thread.stop deprecated?
Because it is inherently unsafe. Stopping a thread causes it to unlock all the monitors that it has locked. (The monitors are unlocked as the ThreadDeath exception propagates up the stack.) If any of the objects previously protected by these monitors were in an inconsistent state, other threads may now view these objects in an inconsistent state. Such objects are said to be damaged. When threads operate on damaged objects, arbitrary behavior can result. This behavior may be subtle and difficult to detect, or it may be pronounced. Unlike other unchecked exceptions, ThreadDeath kills threads silently; thus, the user has no warning that his program may be corrupted. The corruption can manifest itself at any time after the actual damage occurs, even hours or days in the future.

Couldn't I just catch the ThreadDeath exception and fix the damaged object?
In theory, perhaps, but it would vastly complicate the task of writing correct multithreaded code. The task would be nearly insurmountable for two reasons:

  1. A thread can throw a ThreadDeath exception almost anywhere. All synchronized methods and blocks would have to be studied in great detail, with this in mind.
  2. A thread can throw a second ThreadDeath exception while cleaning up from the first (in the catch or finally clause). Cleanup would have to repeated till it succeeded. The code to ensure this would be quite complex.

In sum, it just isn't practical.

What about Thread.stop(Throwable)?
In addition to all of the problems noted above, this method may be used to generate exceptions that its target thread is unprepared to handle (including checked exceptions that the thread could not possibly throw, were it not for this method). For example, the following method is behaviorally identical to Java's throw operation, but circumvents the compiler's attempts to guarantee that the calling method has declared all of the checked exceptions that it may throw:

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

  What should I use instead of Thread.stop?

Most uses of stop should be replaced by code that simply modifies some variable to indicate that the target thread should stop running. The target thread should check this variable regularly, and return from its run method in an orderly fashion if the variable indicates that it is to stop running. (This is the approach that the Java Tutorial has always recommended.) To ensure prompt communication of the stop-request, the variable must be volatile (or access to the variable must be synchronized).

For example, suppose your applet contains the following start, stop and run methods:  

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

   You can avoid the use of Thread.stop by replacing the applet's stop and run methods with:

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

  How do I stop a thread that waits for long periods (e.g., for input)?

That's what the Thread.interrupt method is for. The same "state based" signaling mechanism shown above can be used, but the state change (blinker = null, in the previous example) can be followed by a call to Thread.interrupt, to interrupt the wait:

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

  For this technique to work, it's critical that any method that catches an interrupt exception and is not prepared to deal with it immediately reasserts the exception. We say reasserts rather than rethrows, because it is not always possible to rethrow the exception. If the method that catches the InterruptedException is not declared to throw this (checked) exception, then it should "reinterrupt itself" with the following incantation:

Thread.currentThread().interrupt();

 This ensures that the Thread will reraise the InterruptedException as soon as it is able.

What if a thread doesn't respond to Thread.interrupt?
In some cases, you can use application specific tricks. For example, if a thread is waiting on a known socket, you can close the socket to cause the thread to return immediately. Unfortunately, there really isn't any technique that works in general. It should be noted that in all situations where a waiting thread doesn't respond to Thread.interrupt, it wouldn't respond to Thread.stop either. Such cases include deliberate denial-of-service attacks, and I/O operations for which thread.stop and thread.interrupt do not work properly.

Why are Thread.suspend and Thread.resume deprecated?
Thread.suspend is inherently deadlock-prone. If the target thread holds a lock on the monitor protecting a critical system resource when it is suspended, no thread can access this resource until the target thread is resumed. If the thread that would resume the target thread attempts to lock this monitor prior to calling resume, deadlock results. Such deadlocks typically manifest themselves as "frozen" processes.

What should I use instead of Thread.suspend and Thread.resume?
As with Thread.stop, the prudent approach is to have the "target thread" poll a variable indicating the desired state of the thread (active or suspended). When the desired state is suspended, the thread waits using Object.wait. When the thread is resumed, the target thread is notified using Object.notify.
For example, suppose your applet contains the following mousePressed event handler, which toggles the state of a thread called blinker:

private boolean threadSuspended;

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

  You can avoid the use of Thread.suspend and Thread.resume by replacing the event handler above with:

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

  and adding the following code to the "run loop":

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

  The wait method throws the InterruptedException, so it must be inside a try ... catch clause. It's fine to put it in the same clause as the sleep. The check should follow (rather than precede) the sleep so the window is immediately repainted when the the thread is "resumed." The resulting run method follows:

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

  Note that the notify in the mousePressed method and the wait in the run method are inside synchronized blocks. This is required by the language, and ensures that wait and notify are properly serialized. In practical terms, this eliminates race conditions that could cause the "suspended" thread to miss a notify and remain suspended indefinitely.

While the cost of synchronization in Java is decreasing as the platform matures, it will never be free. A simple trick can be used to remove the synchronization that we've added to each iteration of the "run loop." The synchronized block that was added is replaced by a slightly more complex piece of code that enters a synchronized block only if the thread has actually been suspended:

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

  In the absence of explicit synchronization, threadSuspended must be made volatile to ensure prompt communication of the suspend-request.

The resulting run method is:

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

  

Can I combine the two techniques to produce a thread that may be safely "stopped" or "suspended"?

Yes; it's reasonably straightforward. The one subtlety is that the target thread may already be suspended at the time that another thread tries to stop it. If the stop method merely sets the state variable (blinker) to null, the target thread will remain suspended (waiting on the monitor), rather than exiting gracefully as it should. If the applet is restarted, multiple threads could end up waiting on the monitor at the same time, resulting in erratic behavior.

To rectify this situation, the stop method must ensure that the target thread resumes immediately if it is suspended. Once the target thread resumes, it must recognize immediately that it has been stopped, and exit gracefully. Here's how the resulting run and stop methods look:

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

If the stop method calls Thread.interrupt, as described above, it needn't call notify as well, but it still must be synchronized. This ensures that the target thread won't miss an interrupt due to a race condition.

What about Thread.destroy?

Thread.destroy has never been implemented. If it were implemented, it would be deadlock-prone in the manner of Thread.suspend. (In fact, it is roughly equivalent to Thread.suspend without the possibility of a subsequent Thread.resume.) We are not implementing it at this time, but neither are we deprecating it (forestalling its implementation in future). While it would certainly be deadlock prone, it has been argued that there may be circumstances where a program is willing to risk a deadlock rather than exit outright.

Why is Runtime.runFinalizersOnExit deprecated?

Because it is inherently unsafe. It may result in finalizers being called on live objects while other threads are concurrently manipulating those objects, resulting in erratic behavior or deadlock. While this problem could be prevented if the class whose objects are being finalized were coded to "defend against" this call, most programmers do not defend against it. They assume that an object is dead at the time that its finalizer is called. Further, the call is not "thread-safe" in the sense that it sets a VM-global flag. This forces every class with a finalizer to defend against the finalization of live objects!

下面是中文版翻译:

为什么Thread.stop 被废弃了?

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

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

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

  线程可以在几乎任何地方抛出ThreadDeath 异常。由于这一点,所有的同步方法和 (代码)块将必须被考虑得事无巨细。  线程在清理第一个 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全局标志。这迫使每个带有终结器的类防御活动对象的终结!