如何在Java中中断/停止线程?

16

我正在尝试停止一个线程,但我无法做到:

public class Middleware {

public void read() {
    try {
        socket = new Socket("192.168.1.8", 2001);

        // code .. 

        Scan scan = new Scan();
        thread = new Thread(scan);
        thread.start();

    } catch (UnknownHostException ex) {
        ex.printStackTrace();

    } catch (IOException ex) {
        ex.printStackTrace();
    }
}

class Scan extends Thread {

    public void run() {

        while (true) {
            try {
            // my code goes here

            } catch (IOException ex) {
                thread.currentThread().interrupt();
            }
        }
    }
}

public void stop() {
    Thread.currentThread().interrupt();
}

// get and setters
}

所以,即使我调用了“stop”方法,线程仍然不会停止。它仍然存活着。

我该如何中断/停止这个线程?

更新 (@little approach)

private void tb_startActionPerformed(java.awt.event.ActionEvent evt) {                                         
    Middleware middleware = new Middleware();

    if (tb_start.getText().equals("Start")){
        tb_start.setText("Stop");

        // starting to read rfid tags
        middleware.read();
    }else{
        tb_start.setText("Start");

        // stop reading rfid tags
        middleware.stop();
    }
}

中间件 (Middleware) 类:

public class Middleware {

    private Scan scan;

    public void read() {

        scan = new Scan();
        scan.start();
    }

    private class Scan extends Thread {

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("reading...");
            }
        }
    }

    public void stop() {
        if (scan != null) {
            scan.interrupt();
        }
    }
}

但是当我尝试停止线程时,它没有停止。

上述代码有什么问题?


8个回答

21

没有必要使用volatile标志。相反,只需使用isInterrupted()查询线程的状态即可。另外,为什么要将你的Scan线程对象包装在另一个线程对象中呢?这对我来说似乎完全没有必要。

以下是您应该执行的操作:

public class Middleware {
    private Scan scan;

    public void read() {
        try {
            // do stuff

            scan = new Scan();
            scan.start();
        } catch (UnknownHostException ex) {
            // handle exception
        } catch (IOException ex) {
            // handle exception
        }
    }

    private class Scan extends Thread {

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    // my code goes here
                } catch (IOException ex) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    public void stop() {
        if(scan != null){
            scan.interrupt();
        }
    }
}

这里有一个示例。此外,我不建议扩展Thread


在中断时可能会发生安全异常,除非当前线程正在中断自身(这始终是允许的)。此时将调用该线程的checkAccess方法,这可能会导致抛出SecurityException异常。 - Leonard Brünings
Thread被interrupt()中断后,如何重新启动它,即恢复它。 - Volodymyr Levytskyi
“你真的没有必要使用volatile标志。”其实这并不完全正确。当中断标志被清除的情况相当令人困惑,如果线程中的代码调用任何第三方库,那么调用“theThread.interrupt()”可能实际上并不起作用。 - Peter
@BobbyBrown 我之前回答过这个问题,但如果我没记错的话,它与更喜欢组合而不是继承有关。但作为一个快速而简单的解决方案,这也是可以的。 - mre

11

只需从while循环中return;,线程就会停止,不需要调用stop()或interrupt()。如果你想从外部操作线程,则可以使用此模式并调用requestStop()

class Scan extends Thread {
    private volatile stop = false;
    public void run() {

        while (!stop) {
            try {
            // my code goes here

            } catch (IOException ex) {
                stop = true;
            }
        }
    }

    public void requestStop() {
        stop = true;
    }

}

1
如果在try中有阻塞代码,比如等待连接被接受或scanner.nextLine()等待输入,这段代码仍然能正常工作吗? - Mohammad Jafar Mashhadi
2
requestStop 方法中将 stop 设置为 true 后,您可以添加一个 interrupt() 调用。 - Leonard Brünings
如果您不使用volatile,就不能保证看到字段的最新状态,因为当前线程可能会缓存先前的状态。 - Leonard Brünings

5
通常停止线程的方法是使用一个volatile标志,然后在run方法中进行检查。例如:
class Scan extends Thread {
    volatile stop = false;
    public void run() {

        while (!stop) {
            try {
            // my code goes here

            } catch (IOException ex) {
                thread.currentThread().interrupt();
            }
        }
    }

    public void stop(){
        stop = true;
    }
}

您可以随后调用scan.stop()

1
在你的情况下,你需要在 catch 中将 stop 设置为 true,而不是调用 interrupt。 - Leonard Brünings
如果在try中有阻塞代码,比如等待连接被接受或scanner.nextLine()等待键盘输入,这段代码仍然能正常工作吗? - Mohammad Jafar Mashhadi
为什么要使用volatile,而不是普通的私有变量? - raffian
@raffian 线程拥有变量的本地副本,可能不具备变量的最新值。Volatile 可以确保 stop 不会使用缓存值。 - Jim
@KarlMorrison try catch 是原始提问者的代码。如果你的代码没有抛出 IOException,它是不必要的,可以将其删除。 - Jim
显示剩余2条评论

5

如果您想中断正在运行的线程,则需要访问该线程对象。

thread = new Thread(scan);
thread.start();

那么说
thread.interrupt();

这将中断正在运行的线程。


5

“许多使用stop()的情况应该被替换为仅修改某些变量以指示目标线程应停止运行的代码。” —— java.lang.Thread


1

完全同意Jim的观点。只是要补充一下,如果您的线程在try块内被阻塞,例如在数据流上读取等操作,则也应中断该线程或关闭流。在这两种情况下,线程应该再次被唤醒,然后它将能够看到“stop”布尔值的变化,并通过退出run方法自然地死亡。至少这就是我在服务器的关闭线程中用来终止我的线程所做的。


0
    public void run()
    { 
         while (!Thread.currentThread().isInterrupted()) 
         {
        //do something here
        if(condition)
       {
           Thread.currentThread().interrupt();
       }
   }

2
请问您能否再详细说明一下您的答案?通常最好加上一些解释。 - Bono

0

与其使用 Thread.stop() 或 Thread.interrupt(),你可以使用外部锁。基本上,当你尝试使用内在锁时,大多数情况下对线程进行的任何中断都是不可控的。

可重入锁为你提供以下方法:

lock() 
unlock() 
tryLock() 
lockInterruptibly() 
isHeldByCurrentThread() 
getHoldCount() 

请查看下面的示例

final ReentrantLock reentrantLock = new ReentrantLock();    
    @Override
    public void performTask() {
        reentrantLock.lock();
        try { 
             System.out.println(Thread.currentThread().getName() + ": Lock acquired.");
             System.out.println("Processing...");
             Thread.sleep(2000);
        } catch (InterruptedException e) {
             e.printStackTrace();
        } finally {
             System.out.println(Thread.currentThread().getName() + ": Lock released.");
         reentrantLock.unlock();
            }
    }

这样可以使你的代码更加优雅,更好地处理中断。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接