因为其天生是不安全的。停止一个线程会导致其解锁其上被锁定的所有监视器(监视器以在栈顶产生ThreadDeath异常的方式被解锁)。如果之前被这些监视器保护的任何对象处于不一致状态,其它线程看到的这些对象就会处于不一致状态。这种对象被称为受损的 (damaged)。当线程在受损的对象上进行操作时,会导致任意行为。这种行为可能微妙且难以检测,也可能会比较明显。不像其他未受检的(unchecked)异常, ThreadDeath 悄无声息的杀死及其他线程。因此,用户得不到程序可能会崩溃的警告。崩溃会在真正破坏发生后的任意时刻显现,甚至在数小时或数天之后。
- 线程可以在几乎任何地方抛出 ThreadDeath 异常。由于这一点,所有的同步方法和(代码)块将必须被考虑得事无巨细。
- 线程在清理第一个 ThreadDeath 异常的时候(在 catch 或 finally 语句中),可能会抛出第二个。清理工作将不得不重复直到到其成功。保障这一点的代码将会很复杂。
例如,假设你的 applet 包含了 start 、 stop 和 run 方法:
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();
}
}
为了避免使用 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.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();
}
}