Java线程stop等方法被弃用(Java SE 19 & JDK 19)

22-11-16 banq

Java 中Thread 一些方法被弃用:stop, suspend, resume

为什么被Thread.stop弃用?
因为它本质上是不安全的。停止一个线程会导致它解锁它所锁定的所有监视器(当ThreadDeath异常在堆栈上传播时,这些监视器被解锁)。
如果之前被这些监视器保护的任何对象处于不一致的状态,其他线程现在可能会在不一致的状态下查看这些对象,这样的对象被称为损坏。

当线程对受损对象进行操作时,可能会产生任意的行为。这种行为可能是微妙的,难以检测,也可能是明显的。
与其他未检查的异常不同,ThreadDeath会无声无息地杀死线程;因此,用户没有警告说他的程序可能被破坏。损坏可能在实际损坏发生后的任何时候表现出来,甚至是未来的几个小时或几天。

难道我不能直接捕捉ThreadDeath异常,然后修复受损的对象吗?
理论上来说,也许可以,但这将使编写正确的多线程代码的工作变得非常复杂。由于两个原因,这项任务几乎是不可逾越的。

一个线程几乎可以在任何地方抛出一个ThreadDeath异常。所有的同步方法和块都必须被详细地研究,并牢记这一点。
一个线程可以在清理第一个异常的时候抛出第二个ThreadDeath异常(在catch或final子句中)。清理工作必须重复进行,直到它成功。确保这一点的代码将是相当复杂的。
总而言之,这并不实际。

我应该用什么来代替Thread.stop?
大多数对stop的使用应该被简单地修改一些变量的代码所取代,从而表明目标线程应该停止运行。

例如,假设你的小程序包含以下启动、停止和运行方法。
 

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


你可以通过将用以下方法为来避免使用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();
        }
    }



应定期检查该变量,如果该变量表明它应停止运行,则应有序地从其运行方法中返回。为了确保及时传达停止请求,该变量必须是易失性的
volatile 
(或者对该变量的访问必须是同步的)。

如何停止一个长时间等待的线程(例如,等待输入)?
这就是Thread.interrupt方法的作用。可以使用上面显示的同样的 "基于状态 "的信号机制,但是状态变化(在前面的例子中,Blinker = null)之后可以调用Thread.interrupt,以中断等待过程。

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


为了使这种技术发挥作用,关键是任何捕捉到中断异常而又不准备处理的方法都要立即重新断言该异常。我们说重新断言而不是重新抛出,是因为并不总是能够重新抛出异常。如果捕获中断异常的方法没有被声明抛出这个(被检查的)异常,那么它应该用下面 "重新中断自己":

Thread.currentThread().interrupt();


如果一个线程不响应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来通知目标线程。

例如,假设你的小程序包含以下mousePressed事件处理程序,它可以切换一个叫做blinker的线程的状态。

private boolean threadSuspended;

    Public void mousePressed(MouseEvent e) {
        e.consume();

        if (threadSuspended)
            blinker.resume();
        else
            blinker.suspend();  // DEADLOCK-PRONE!

        threadSuspended = !threadSuspended;
    }


替换为:

private volatile boolean threadSuspended;

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

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


详细点击标题

我能否将这两种技术结合起来,产生一个可以安全 "停止 "或 "暂停 "的线程?
是的,这是很直接的。一个微妙之处在于,当另一个线程试图停止它时,目标线程可能已经被暂停了。如果停止方法只是将状态变量(blinker)设置为null,那么目标线程将保持暂停状态(在监视器上等待),而不是像它应该的那样优雅地退出。如果小程序被重新启动,多个线程可能最终会同时在监视器上等待,从而导致不稳定的行为。
为了纠正这种情况,停止方法必须确保目标线程在被暂停时立即恢复。一旦目标线程恢复,它必须立即认识到它已经被停止,并优雅地退出。下面是产生的运行和停止方法的样子。

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,但它仍然必须是同步的。这可以确保目标线程不会因为竞赛条件而错过中断。