Java 线程原语弃用
为什么 Thread.stop
被弃用并且停止线程的能力被移除?
因为它本质上是不安全的。停止线程会导致它解锁所有已锁定的监视器。 (随着 ThreadDeath
异常向上传播到堆栈,监视器被解锁。)如果之前受这些监视器保护的任何对象处于不一致状态,则其他线程可能会以不一致状态查看这些对象。据说这样的物体是damaged.当线程对损坏的对象进行操作时,可能会导致任意行为。这种行为可能很微妙且难以察觉,也可能很明显。与其他未经检查的异常不同,ThreadDeath
静静地杀死了线程;因此,用户没有收到他们的程序可能已损坏的警告。损坏可能会在实际损坏发生后的任何时间出现,甚至可能在未来数小时或数天后出现。
难道我不能刚刚抓住 ThreadDeath
并修复损坏的对象吗?
理论上,也许,但这会使编写正确的多线程代码的任务变得复杂。由于以下两个原因,这项任务几乎无法完成:
- 线程可能会抛出
ThreadDeath
异常几乎任何地方.考虑到这一点,必须非常详细地研究所有同步方法和块。 - 线程在从第一个(在
catch
或finally
子句中)清理时可能抛出第二个ThreadDeath
异常。必须重复清理直到成功。确保这一点的代码将非常复杂。
我应该用什么代替 Thread.stop
?
stop
的大多数用途应该替换为简单地修改某些变量以指示目标线程应停止运行的代码。目标线程应该定期检查这个变量,如果变量指示它要停止运行,则从它的 run 方法有序地返回。为确保停止请求的及时通信,变量必须是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() { while (true) { try { Thread.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 { Thread.sleep(interval); } catch (InterruptedException e){ } repaint(); } }
如何停止等待很长时间(例如,等待输入)的线程?
这就是 Thread.interrupt
方法的用途。可以使用上面显示的相同“基于状态”的信号机制,但状态更改(blinker = null
,在前面的示例中)可以通过调用 Thread.interrupt
来中断等待:
public void stop() { Thread moribund = waiter; waiter = null; moribund.interrupt(); }要使此技术发挥作用,关键是任何捕获中断异常但不准备立即处理它的方法都会重新断言该异常。我们说 reasserts 而不是 rethrows ,因为并不总是可以重新抛出异常。如果捕获
InterruptedException
的方法未声明抛出此(已检查)异常,则它应该使用以下咒语“重新中断自身”:
Thread.currentThread().interrupt();这确保线程将尽快重新引发
InterruptedException
。
如果线程不响应 Thread.interrupt
怎么办?
在某些情况下,您可以使用特定于应用程序的技巧。例如,如果一个线程正在等待一个已知的套接字,您可以关闭该套接字以使线程立即返回。不幸的是,确实没有任何通用的技术。 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. 这种情况包括蓄意的拒绝服务攻击,以及 thread.stop 和 thread.interrupt 无法正常工作的 I/O 操作。
为什么 Thread.suspend
和 Thread.resume
被弃用并且暂停或恢复线程的能力被移除?
Thread.suspend
本质上容易发生死锁。如果目标线程在挂起时锁定保护关键系统资源的监视器,则在目标线程恢复之前,任何线程都无法访问该资源。如果打算恢复目标线程的线程在调用 resume
之前尝试锁定监视器,则会导致死锁。这种死锁通常表现为“冻结”进程。
我应该使用什么来代替 Thread.suspend
和 Thread.resume
?
与 Thread.stop
一样,谨慎的方法是让“目标线程”轮询一个指示线程所需状态(活动或挂起)的变量。当所需状态挂起时,线程使用 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; }您可以避免使用
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.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 volatile boolean threadSuspended; public void run() { while (true) { try { Thread.sleep(interval); if (threadSuspended) { synchronized(this) { while (threadSuspended) wait(); } } } catch (InterruptedException e){ } repaint(); } }
我能否结合这两种技术来生成可以安全地“停止”或“暂停”的线程?
是的,这相当简单。一个微妙之处是目标线程可能在另一个线程试图停止它时已经挂起。如果stop
方法仅将状态变量 (blinker
) 设置为 null,则目标线程将保持挂起状态(等待监视器),而不是正常退出。如果重新启动小程序,多个线程可能会同时等待监视器,从而导致不稳定的行为。
要纠正这种情况,stop
方法必须确保目标线程在挂起时立即恢复。一旦目标线程恢复,它必须立即识别它已经停止,并优雅地退出。以下是生成的 run
和 stop
方法的外观:
public void run() { Thread thisThread = Thread.currentThread(); while (blinker == thisThread) { try { Thread.sleep(interval); synchronized(this) { while (threadSuspended && blinker==thisThread) wait(); } } catch (InterruptedException e){ } repaint(); } } public synchronized void stop() { blinker = null; notify(); }如果
stop
方法调用 Thread.interrupt
,如上所述,它也不需要调用 notify
,但它仍然必须同步。这可确保目标线程不会因竞争条件而错过中断。