为什么是线程停止不赞成?
因为它本质上是不安全的。停止线程会使其解锁其锁定的所有监视器。(当ThreadDeath异常在堆栈上传播时,监视器将被解锁。)如果以前受这些监视器保护的任何对象处于不一致状态,其他线程现在可能会以不一致的状态查看这些对象。据说这些物品被损坏了。当线程对损坏的对象进行操作时,可能会导致任意行为。这种行为可能是微妙的,很难被发现,也可能是被宣布的。与其他未经检查的异常不同,ThreadDeath无声地杀死线程;因此,用户没有警告他的程序可能已损坏。腐败在实际损害发生后的任何时候都会显现出来,甚至在未来的几个小时或几天内也是如此。
为什么是线程停止我不能捕获ThreadDeath异常并修复损坏的对象吗?
从理论上讲,可能是这样,但这会使编写正确的多线程代码的任务变得非常复杂。这项任务几乎无法完成,原因有两个:
1.线程几乎可以在任何地方抛出ThreadDeath异常。所有同步的方法和块都必须非常详细地研究,记住这一点。
2.当从第一个线程(在catch或finally子句中)清除时,线程可以抛出第二个ThreadDeath异常。清理工作必须重复进行,直到成功为止。确保这一点的代码将非常复杂。
总之,这是不实际的。
那如何线程停止(可丢弃的)?
除上述所有问题外,此方法还可用于生成目标线程未准备好处理的异常(包括如果没有此方法,则线程不可能抛出的检查异常)。例如,以下方法在行为上与Java的抛出操作相同,但避免了编译器试图确保调用方法声明了它可能抛出的所有检查异常:
static void sneakyThrow(Throwable t) {
Thread.currentThread().stop(t);
}
我应该用什么来代替线程停止?
大多数停止的用法应该被简单地修改一些变量以指示目标线程应该停止运行的代码所取代。目标线程应该定期检查该变量,如果变量指示要停止运行,则应该以有序的方式从它的run方法返回。为了确保停止请求的快速通信,变量必须是易失性的(或者对变量的访问必须是同步的)。例如,假设您的applet包含以下启动、停止和运行方法:
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();
}
}
可以通过将applet的停止和运行方法替换为:
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();
}
}
如何停止等待很长时间的线程(例如,等待输入)?
这就是线程.中断方法的作用所在。可以使用相同的“基于状态”的信令机制,但状态更改(在前面的示例中,闪烁值=NULL)之后可以调用Thread.中断,以中断等待:
public void stop() {
Thread moribund = waiter;
waiter = null;
moribund.interrupt();
}
要使此技术有效,任何捕获中断异常且不准备立即处理该异常的方法都必须重新声明异常。我们说的是重抛而不是重新抛出,因为重新抛出异常并不总是可能的。如果未声明捕获InterruptedException的方法抛出此(已检查)异常,则应使用以下咒语“重新中断自身”:
Thread.currentThread().interrupt();
这将确保线程一旦能够恢复InterruptedException。
如果线程没有响应线程。中断怎么办?
在某些情况下,您可以使用特定于应用程序的技巧。例如,如果线程正在等待已知的套接字,则可以关闭套接字以使线程立即返回。不幸的是,实际上没有任何技术在一般情况下起作用。应该注意的是,在所有等待线程不响应Thread.中断的情况下,它也不会响应Thread.Stop。这种情况包括蓄意拒绝服务攻击,以及I/O操作,这些操作的线程.停止和线程.中断不能正常工作。
为什么线程暂停和线程恢复不再受欢迎?
线程。挂起天生容易死锁。如果目标线程在监视器上持有一个锁,在关键系统资源挂起时保护该资源,则在目标线程恢复之前,任何线程都不能访问该资源。如果要恢复目标线程的线程在调用resume之前尝试锁定此监视器,则会导致死锁。这种死锁通常表现为“冻结”进程。
我应该用什么来代替线程暂停和线程恢复?
与Thread.top一样,谨慎的方法是让“目标线程”轮询一个变量,该变量指示线程的所需状态(活动或挂起)。当所需的状态被挂起时,线程将使用Object.Wait等待。
当线程被恢复时,目标线程将被使用Object.Notification通知。例如,假设您的applet包含以下mousePress事件处理程序,该处理程序将切换名为闪烁器的线程的状态:
private boolean threadSuspended;
Public void mousePressed(MouseEvent e) {
e.consume();
if (threadSuspended)
blinker.resume();
else
blinker.suspend(); // DEADLOCK-PRONE!
threadSuspended = !threadSuspended;
}
可以通过将上面的事件处理程序替换为:
public synchronized void mousePressed(MouseEvent e) {
e.consume();
threadSuspended = !threadSuspended;
if (!threadSuspended)
notify();
}
并将以下代码添加到“Run循环”中:
synchronized(this) {
while (threadSuspended)
wait();
}
等待方法抛出InterruptedException,因此它必须在一次尝试中.捕获条款。把它和睡眠放在同一个条款里是可以的。检查应该跟随(而不是在睡眠之前),因此当线程“恢复”时,立即重新绘制窗口。得到的Run方法如下:
public void run() {
while (true) {
try {
Thread.sleep(interval);
synchronized(this) {
while (threadSuspended)
wait();
}
} catch (InterruptedException e){
}
repaint();
}
}
注意,mousePress方法中的Notification和Run方法中的Wait都在同步块中。这是语言所要求的,并确保等待和通知被正确序列化。实际上,这消除了可能导致“挂起”线程丢失通知并无限期挂起的争用条件。
虽然Java中的同步成本随着平台的成熟而降低,但它永远不会是免费的。可以使用一个简单的技巧来删除我们添加到“Run循环”每次迭代中的同步。添加的同步块被稍微复杂一些的代码替换,只有在线程实际挂起时才进入同步块:
if (threadSuspended) {
synchronized(this) {
while (threadSuspended)
wait();
}
}
在没有显式同步的情况下,必须使线程悬挂不稳定,以确保暂停请求的及时通信。结果的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();
}
如上文所述,如果停止方法调用Thread.中断,它也不必调用Notification,但它仍然必须是同步的。这可以确保目标线程不会因为争用条件而错过中断。
那如何线程销毁?
线程破坏性从来没有实现过,并且已经被废弃了。如果实现了它,就会以线程销毁的方式出现死锁。(实际上,它大致相当于Thread.休会,而不可能出现后续的Thread.Response)。
为什么是运行时.runFinalizersOnExit不赞成?
因为它本质上是不安全的。它可能导致在活动对象上调用终结器,而其他线程则同时操作这些对象,从而导致异常行为或死锁。虽然这个问题是可以避免的,如果其对象正在最后确定的类被编码为“防御”这个调用,大多数程序员不抵抗它。它们假设对象在调用终结器时已经死亡。此外,调用并不是“线程安全”,因为它设置了VM全局标志。这迫使每个类使用终结器来防御活动对象的终结!