为什么Thread.stop不推荐使用? 如何正确停止一个线程

 

为什么Thread.stop不推荐使用?

因为它本质上是不安全的。停止线程会导致它解锁所有已锁定的监视器。(当ThreadDeath异常在堆栈中传播时,监视器被解锁。)如果之前由这些监视器保护的对象中的任何一个处于不一致状态,则其他线程现在可以以不一致的状态查看这些对象。据称这些物体被 损坏当线程操作受损对象时,可能导致任意行为。这种行为可能微妙且难以检测,或者可能会发音。与其他未经检查的异常不同,可以 ThreadDeath静默地杀死线程; 因此,用户没有警告他的程序可能被损坏。腐败现象可能会在实际损害发生后随时出现,甚至可能在未来数小时甚至数天。


我能不能抓住ThreadDeath异常并修复损坏的物体?

理论上讲,也许,但是这会使编写正确的多线程代码的任务大大复杂化。由于两个原因,这项任务将几乎无法克服:

  1. 线程几乎可以在任何地方抛出ThreadDeath异常 考虑到这一点,所有同步的方法和块都必须进行详细研究。
  2. 一个线程可以ThreadDeath在清理第一个异常的同时抛出第二个异常(在catch或 finally子句中)。清理将不得不重复,直到成功。确保这一点的代码将非常复杂。
总之,这只是不实际的。

那怎么样Thread.stop(Throwable)

除了上面提到的所有问题之外,该方法还可用于生成目标线程未准备好处理的异常(包括线程不可能抛出的检查异常,如果不是这种方法)。例如,下面的方法在行为上与Java的throw操作相同 ,但绕过了编译器试图保证调用方法已经声明了可能抛出的所有检查过的异常:

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

我应该用什么来代替Thread.stop

大多数用途stop应该被代码替换,该代码简单地修改某些变量以指示目标线程应该停止运行。目标线程应该定期检查这个变量,并且如果变量指示它将停止运行,则从其run方法有序地返回。为确保停止请求的即时通信,变量必须是 易失性的(或者必须同步对变量的访问)。

例如,假设你的小应用程序包含以下 startstoprun 方法:

    private thred blinker;

    public void start(){
        blinker = new Thread(this);
        blinker.start();
    }

    public void stop(){
        blinker.stop(); //不安全!
    }

    public void run(){
        while(true){
            tru{
                了Thread.sleep(2000);
            } catch(InterruptedException e){
            }
            catch();
        }
    }
您可以通过以下方式避免使用 Thread.stop applet  stop run 方法:
    私人易变线程闪烁器;

    public void stop(){
        blinker = null;
    }

    public void run(){
        Thread thisThread = Thread.currentThread();
        while(blinker == thisThread){
           try{
                 Thread.sleep(2000);
            } catch(InterruptedException e){
            }
            catch();
        }
    }

如何停止等待很长时间的线程(例如,用于输入)?

这就是该Thread.interrupt方法的用途。可以使用上面所示的相同的“基于状态”的信令机制,但状态改变(blinker = null在前面的例子中)可以跟随一个呼叫Thread.interrupt来中断等待:

    public void stop(){ 
        waiter = null;
        moribund.interrupt();
    }
为了使这种技术起作用,捕获中断异常并且不准备立即处理它的任何方法重新确定异常是非常关键的。我们说 reasserts  而不是 rethrows ,因为它不总是可能重新抛出异常。如果捕获该方法的方法  InterruptedException 没有被声明为抛出这个(checked)异常,那么它应该用下面的咒语“重新打开自己”:
    Thread.currentThread()中断();
这确保线程将 InterruptedException 尽快重新启动 

如果一个线程不响应 Thread.interrupt呢?

在某些情况下,您可以使用特定于应用程序的技巧。例如,如果线程正在等待已知套接字,则可以关闭套接字以使线程立即返回。不幸的是,真的没有任何一般的技术可行。应该注意的是,在等待线程没有响应的所有情况下Thread.interrupt,它都不会响应Thread.stop这种情况包括故意的拒绝服务攻击以及thread.stop和thread.interrupt无法正常工作的I / O操作。


为什么Thread.suspend和 Thread.resume弃用?

Thread.suspend本质上容易出现死锁。如果目标线程在监视器上保留一个锁定,以便在暂停时保护关键系统资源,则在目标线程恢复之前,线程无法访问此资源。如果在调用之前将恢复目标线程的线程尝试锁定此监视器resume,则会导致死锁结果。这种僵局通常表现为“冻结”的过程。


我应该用什么来代替Thread.suspend和 Thread.resume

与此同时Thread.stop,谨慎的做法是让“目标线程”轮询一个指示线程所需状态的变量(活动或挂起)。当期望的状态被挂起时,线程等待使用Object.wait当线程恢复时,使用通知目标线程 Object.notify

例如,假设您的applet包含以下mousePressed事件处理程序,该处理程序切换所调用线程的状态blinker

    私有布尔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(2000);

                synchronized(this){
                    while(threadSuspended)
                        wait();
                }
            } catch(InterruptedException e){
            }
            catch();
        }
    }
请注意, notify mousePressed  方法和 wait run 方法都在里面 synchronized 块。这是语言所要求的,并确保 wait 并  notify 正确地序列化。实际上,这消除了可能导致“暂停”线程错过 notify 并无限期停止的竞态条件

虽然随着平台成熟,Java中的同步成本逐渐下降,但永远不会免费。可以使用一个简单的技巧来删除我们添加到“运行循环”每次迭代的同步。被添加的同步块被一段稍微更复杂的代码替换,该代码只有在该线程实际上被暂停时才进入同步块:

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

在没有显式同步的情况下, 必须使threadSuspended 易变,以确保暂停请求的即时通信。

由此产生的 run 方法是:
    private volatile boolean threadSuspended;

    public void run(){
        while(true){
            try{
                了Thread.sleep(2000);

                if(threadSuspended){
                    synchronized(this){
                        while(threadSuspended)
                            wait();
                    }
                }
            } catch(InterruptedException e){
            }
            重绘();
        }
    }

我可以结合使用这两种技术来生成可安全“停止”或“暂停”的线程吗?

是的,这是相当简单的。其中一个微妙之处在于,当另一个线程试图阻止它时,目标线程可能已经被暂停。如果 停止 方法仅将状态变量( blinker )设置为空,则目标线程将保持挂起(等待在监视器上),而不是按照它应该优雅地退出。如果applet重新启动,多个线程可能最终同时在显示器上等待,导致不稳定的行为。

为了纠正这种情况,stop方法必须确保目标线程在挂起时立即恢复。一旦目标线程恢复,它必须立即识别出它已被停止,并正常退出。下面是结果 运行停止方法的外观:

    public void run(){
        Thread thisThread = Thread.currentThread();
        while(blinker == thisThread){
            try{
                 Thread.sleep(2000);

                synchronized(this){
                    while(threadSuspended && blinker == thisThread)
                        wait();
                }
            } catch(InterruptedException e){
            }
           catch();
        }
    }

    public synchronized void stop(){
        blinker = null;
        notify();
    }
如果 stop 方法调用 Thread.interrupt ,如上所述,它也不需要调用 notify ,但它仍然必须同步。这确保了目标线程不会因竞争状态而错过中断。

那怎么样Thread.destroy

Thread.destroy 从未实施,并已被弃用。如果它被实现了,那么它将会以如下方式出现死锁 Thread.suspend (事实上​​,这大致相当于 Thread.suspend 没有后续的可能性 Thread.resume 。)

为什么Runtime.runFinalizersOnExit 不推荐使用?

因为它本质上是不安全的。这可能会导致终结器在活动对象上被调用,而其他线程同时操作这些对象,从而导致不稳定的行为或死锁。如果正在完成对象的类被编码为“防御”这个调用,那么可以防止这个问题,但大多数程序员 不会 为此辩护。他们假设一个对象在其终结器被调用时已经死亡。

此外,从设置VM全局标志的意义上讲,调用不是“线程安全的”。这迫使每个课程都带有一个终结者来抵御活体对象的终结!


你可能感兴趣的:(java)