【问题标题】:Thread won't stop after interrupting or stopping中断或停止后线程不会停止
【发布时间】:2015-08-12 21:02:12
【问题描述】:

我在停止线程时遇到了一点麻烦。我有扫描文件夹中文件的方法。我想要停止此扫描的按钮。按下此按钮后,线程将停止(扫描将停止)并且程序将重置。我试过命令thread.stop();,它有时会起作用(它不再起作用了)。所以我在 * 上阅读了一些主题并尝试了thread.interrupt(); 我的代码然后看起来像这样:

public class myApp extends javax.swing.JFrame {
  Thread thread;
  int fileCount = 0;
  String path = "C:\\Program Files";

    public void scanningMethod() {
     thread = new Thread(new Runnable() {
        public void run() {
         while(!thread.interrupted()){
             //Recursion method that counting files
             dirScan(path);
           }  
        }    
     });
   thread.start();
  }

  private void stopButtonActionPerformed(java.awt.event.ActionEvent evt) {
      //generated button in netBeans
      thread.interrupt();
      thread.stop();
   }

 private void dirScan(String dirPath) {
    File[] podSoubory = new File(dirPath).listFiles();

    for (int i = 0; i < podSoubory.length; i++) {
        if (podSoubory[i].isDirectory()) {
            String tempPath = podSoubory[i].getAbsolutePath();
            System.out.println(tempPath);

        if (podSoubory[i].isFile()) {
            fileCount++;
        }
    }
  }
}

另一种停止方法仅包含 thread.interrupt();(它有 actionListener 和其他东西)。

可能我做错了什么。你能帮我告诉我点击按钮后如何停止这个正在运行的线程吗? (我知道如何创建可点击按钮)。

【问题讨论】:

  • 为了帮助我们充分了解您的问题,请创建并发布您的minimal reproducible example。这不是您的完整程序,这对我们来说可能太多了,但它是一个可编译和可运行的最小示例程序,不需要外部资源(例如文件或图像),或者如果它确实需要我们可以轻松获得的这些资源,我们可以运行和修改这些资源,并为我们展示您的错误。
  • 在方法中本地声明线程时,如何中断线程?
  • 另请注意,1)我看不到您发布的代码如何获得对感兴趣线程的引用,从而调用 thread.interrupt(),2)如果这是从 Swing GUI 调用的,请考虑使用 SwingWorker 来帮助生成后台线程,因为这可以让您的后台代码与您的 Swing GUI 一起工作。另请注意,SwingWorker 有一个 cancel() 方法,可能对您的目的有用。并且 3) 我确定您已经阅读过不要使用 Thread#stop(),这是危险的代码——我在这里告诉你,你所阅读的内容是真实的——不要调用这个方法。
  • 我的程序现在很乱。我的问题是调用 thread.stop(); 后正在运行的线程不会停止;或 thread.interrupt();但是我会在github上添加链接。
  • But I'll add link on github. -- 这不是我们要求或需要的。同样,我们不想看到整个混乱的计划,因为你会要求志愿者付出太多的努力和工作,而我们就是这样。请阅读或重新阅读我第一篇文章中的链接。在此处发布您的相关minimal reproducible example代码和您的问题。

标签: java multithreading swing user-interface


【解决方案1】:

至于您的主要问题,即为什么您的代码无法正常工作,对不起,但我没有答案,因为不幸的是,我没有时间仔细分析您的所有代码。我会说,尽管您的代码是危险代码(如您所知),这不仅是因为您调用 Thread#stop(),还因为您的代码不遵守 Swing 线程规则,这些规则要求大多数 Swing 调用都在 Swing 事件上进行线。您正在调用一些关键的 Swing 方法,包括后台线程中的 setText(...) 方法,这可能会导致间歇性非常难以调试线程错误。

我建议:

  • 出于多种原因,将 SwingWorkers 用于您的后台线程,但主要是为了您可以将后台线程中的数据安全地发布/处理到 GUI 中。
  • 如果你想杀死你的 SwingWorker,调用它的cancel(true) 方法,传入一个true 参数来允许这个调用取消正在运行的代码。
  • 在 SwingWorker 中,检查 isCancelled() 状态。
  • 您希望后台线程的任何文本或数据都应传递到 SwingWorker 的 doInBackground() 方法中的 publish(...) 调用。
  • 然后 SwingWorker 可以在其 process(...) 方法中处理这些数据,该方法保证在 Swing 事件线程上被调用。

例如:

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.concurrent.ExecutionException;

import javax.swing.*;

public class InterruptedGui {
    private static final int GAP = 3;
    private MyWorker myWorker;
    private StartWorkerAction startWorkerAxn = new StartWorkerAction(this,
            "Worker");
    private StopAllAction stopAllAction = new StopAllAction(this, "Stop All");
    private JLabel statusLabel = new JLabel("");
    private JTextArea finalTextArea = new JTextArea(20, 40); 
    private JPanel mainPanel = new JPanel();

    public InterruptedGui() {
        finalTextArea.setFocusable(false);
        JPanel buttonPanel = new JPanel(new GridLayout(1, 0, GAP, 0));
        buttonPanel.add(new JButton(startWorkerAxn));
        buttonPanel.add(new JButton(stopAllAction));

        JPanel statusPanel = new JPanel();
        statusPanel.setLayout(new BoxLayout(statusPanel, BoxLayout.LINE_AXIS));
        statusPanel.add(new JLabel("Status: "));
        statusPanel.add(statusLabel);

        mainPanel.setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
        mainPanel.setLayout(new BorderLayout());
        mainPanel.add(buttonPanel, BorderLayout.PAGE_START);
        mainPanel.add(new JScrollPane(finalTextArea,
                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED));

        mainPanel.add(statusPanel, BorderLayout.PAGE_END);
    }

    public JComponent getMainPanel() {
        return mainPanel;
    }

    public void setStatus(String text) {
        statusLabel.setText(text);
    }

    public MyWorker getMyWorker() {
        return myWorker;
    }

    public void setMyWorker(MyWorker myWorker) {
        this.myWorker = myWorker;
    }

    public void finalTextAreaSetText(String text) {
        finalTextArea.setText(text);
    }

    private static void createAndShowGui() {
        JFrame frame = new JFrame("Interrupted Gui");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(new InterruptedGui().getMainPanel());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGui();
            }
        });
    }
}

class MyWorker extends SwingWorker<String, String> {
    private static final long SLEEP_TIME = 100;
    private static final int MAX_COUNTER = 100;
    private int counter = 0;
    private InterruptedGui gui;

    public MyWorker(InterruptedGui gui) {
        this.gui = gui;
    }

    @Override
    protected String doInBackground() throws Exception {
        StringBuilder sb = new StringBuilder();
        while (counter < MAX_COUNTER && !isCancelled()) {
            counter++;
            String statusText = "Counter is " + counter;
            sb.append(statusText + "\n");
            publish(statusText);
            Thread.sleep(SLEEP_TIME);
        }
        return sb.toString();
    }

    @Override
    protected void process(List<String> chunks) {
        for (String statusText : chunks) {
            gui.setStatus(statusText);
        }
    }
}

@SuppressWarnings("serial")
class StartWorkerAction extends AbstractAction {
    private InterruptedGui gui;

    public StartWorkerAction(InterruptedGui gui, String name) {
        super(name);
        this.gui = gui;
        int mnemonic = (int) name.charAt(0);
        putValue(MNEMONIC_KEY, mnemonic);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        MyWorker myWorker = gui.getMyWorker();
        if (myWorker != null && !myWorker.isDone()) {
            return; // still running current worker
        }
        gui.finalTextAreaSetText("");
        myWorker = new MyWorker(gui);
        gui.setMyWorker(myWorker);
        myWorker.addPropertyChangeListener(new WorkerPropertyListener(gui));
        myWorker.execute();
    }
}

class WorkerPropertyListener implements PropertyChangeListener {
    private InterruptedGui gui;

    public WorkerPropertyListener(InterruptedGui gui) {
        this.gui = gui;
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
            MyWorker myWorker = gui.getMyWorker();
            if (myWorker != null && !myWorker.isCancelled()) {
                try {
                    String finalText = myWorker.get();
                    gui.finalTextAreaSetText(finalText);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

@SuppressWarnings("serial")
class StopAllAction extends AbstractAction {
    private InterruptedGui gui;

    public StopAllAction(InterruptedGui gui, String name) {
        super(name);
        this.gui = gui;
        int mnemonic = (int) name.charAt(0);
        putValue(MNEMONIC_KEY, mnemonic);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        MyWorker myWorker = gui.getMyWorker();
        if (myWorker == null) {
            return;
        }
        myWorker.cancel(true);
    }
}

【讨论】:

  • 我在开始之前尝试使用Swing Worker,但失败了。我对此了解不多。感谢您的示例,我会检查它。
【解决方案2】:

您对线程中断的工作原理有一个误解,当您调用 Thread#interrupt 时,所发生的只是在 Thread 实例中引发了 flag,您可以使用 interruptedisInterrupted 来检查它。

在您的代码中,您有一个for-loop

for (int i = 0; i < podSoubory.length; i++) {
    if (podSoubory[i].isDirectory()) {
        String tempPath = podSoubory[i].getAbsolutePath();
        System.out.println(tempPath);

    if (podSoubory[i].isFile()) {
        fileCount++;
    }
}

这意味着在此 for-loop 存在之前,不会评估 while(!thread.interrupted()){ 语句。

您需要做的是在代码中的周期性点测试isInterrupted,例如...

for (int i = 0; i < podSoubory.length && !Thread.currentThread().isInterrupted(); i++) {
    if (podSoubory[i].isDirectory()) {
        String tempPath = podSoubory[i].getAbsolutePath();
        System.out.println(tempPath);

        if (podSoubory[i].isFile()) {
            fileCount++;
        }
    }
}

这增加了对isInterrupted 的检查,这是导入的,因为isInterrupted 不会像Thread#interrupted 那样清除中断标志,这允许代码的其他部分也可以进一步测试interrupted 标志.

当您中断 Thread 时,for-loopwhile-loop 都会检查 interrupted 标志的状态,并允许两个循环退出。

作为一个可运行的示例...

import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class MyApp {

    public static void main(String[] args) {
        new MyApp();
    }

    public MyApp() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {

            setLayout(new GridBagLayout());
            JButton stop = new JButton("Stop");
            stop.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    thread.interrupt();
                    // Join is used here to prove a point, be careful
                    // with using this within the context of the EDT
                    try {
                        thread.join();
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                    stop.setEnabled(false);
                }
            });
            add(stop);

            scanningMethod();
        }

        Thread thread;
        int fileCount = 0;
        String path = "C:\\Program Files";

        public void scanningMethod() {
            thread = new Thread(new Runnable() {
                public void run() {
                    while (!thread.isInterrupted()) {
                        //Recursion method that counting files
                        dirScan(path);
                        System.out.println(thread.isInterrupted());
                    }
                }
            });
            thread.start();
        }

        private void dirScan(String dirPath) {
            File[] podSoubory = new File(dirPath).listFiles();

            for (int i = 0; i < podSoubory.length && !Thread.currentThread().isInterrupted(); i++) {
                if (podSoubory[i].isDirectory()) {
                    String tempPath = podSoubory[i].getAbsolutePath();
                    System.out.println(tempPath);

                    if (podSoubory[i].isFile()) {
                        fileCount++;
                    }
                }
            }

        }
    }

}

您可能想查看Concurrency in Java 了解更多详情

另外,Thread#stop 已被弃用,不应使用,来自JavaDocs...

已弃用。这种方法本质上是不安全的。停止线程 Thread.stop 使其解锁所有已锁定的监视器 (作为未经检查的ThreadDeath 异常的自然结果 向上传播堆栈)。如果任何先前受保护的对象 通过这些监视器处于不一致的状态,损坏的对象 对其他线程可见,可能导致任意 行为。停止的许多用法应该由简单的代码代替 修改一些变量以指示目标线程应该停止 跑步。目标线程应该定期检查这个变量,并且 如果变量从它的 run 方法中有序地返回 表示要停止运行。如果目标线程等待 长时间(例如,在条件变量上),中断 方法应该用于中断等待。有关详细信息,请参阅 Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?.

【讨论】:

  • 我认为我在 for 循环中只能有 1 个条件。但我明白你的观点和想法。我明天试试。现在我了解了它是如何工作的,并且我看到了问题所在(可能)。非常感谢!明天我会在这里评论它是怎么回事。
  • 就像我说的,我不能在循环中使用超过 1 个条件,但我添加了额外的 2 个 if 语句。他们检查了!thread.isInterrupted()。一个在循环中,一个在方法的开头。但是在调用thread.interrupt() 之后,即使它应该跳过,循环仍然会运行。可能我必须在 SwingWorker 上重做它。 + 我已经删除了thread.stop()
【解决方案3】:

我建议通过 Runnable 接口实现一个线程。在这种情况下,您将需要实现 run() 和 kill() 方法。引入局部共享变量作为标志。在 kill 方法中将标志设置为 false,在 run 方法中执行操作,直到标志为 true。

一般来说,我建议熟悉在这种情况下可能对您有帮助的等待/通知机制以及 Java Executioner 框架。

最重要的是,请在 * 中搜索类似的问题,他们之前已经回答过了。例如,Java: How to stop thread?

希望这会有所帮助。

【讨论】: