如何在java中的动作侦听器中启动/恢复和停止/暂停线程

Clo*_*ker 8 java swing multithreading

我正在用java编写一个简单的多线程练习。我需要做的就是制作一个带有两个按钮(“开始”和“结束”)的 JFrame。如果用户点击“开始”按钮,控制台将开始打印“打印”。如果单击“结束”,控制台将停止打印。再次单击“开始”将恢复打印。

这是我的代码(不相关部分未显示):

//import not shown

public class Example extends JFrame implements Runnable {
    private static boolean print, started;//print tells whether the thread should keep printing 
                                          //things out, started tells whether the thread has been 
                                          //started 
    private JButton start;//start button
    private JButton end;//end button
    private static Thread thr;//the thread that is going to do the printing
    //other fields not shown

    public Example(String title) {
        Container c = getContentPane();
        //set up the JFrame
        //...parts not shown
        start = new JButton("Start");
        end = new JButton("End");
        c.add(start);
        c.add(end);
        //add the actionListner for the buttons
        start.addActionListener(new ActionListener() {
         
            @Override
            public void actionPerformed(ActionEvent e) {
                if (started == false) {
                    thr.start();// if the thread has not been started, start the thread
                    started = true;
                }else{//otherwise waken the thread. This is to prevent IllegalThreadStateException.
                    thr.notify();
                }
                print = true;//should print things out
            }
        });
        end.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                
                if(started) {//This action won't pause the thread if its not yet started
                    try {
                        thr.wait();//pause the thread
                    } catch (InterruptedException e1) {
                        // TODO Auto-generated catch block
                        e1.printStackTrace();
                    }
                }
                print = false;//should stop printing
            }
        });
        validate();
        setVisible(true);

    }

    @Override
    public void run() {//override run() method
        while (print) {
            System.out.println("Printing");//print out "Printing"
        }
    }

    public static void main(String[] args) {//main method
        Example ex = new Example("My Frame");//instantiate the frame
        thr = new Thread(ex);//make a new thread;
        started = false;//the thread has not been started, so set started to false;
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,一旦单击开始按钮,控制台就永远不会停止打印。我不断收到 IllegalMonitorStateException。是什么导致了这个问题?我找不到错误,因为所有部分在逻辑上似乎都是正确的。任何帮助将不胜感激。

tha*_*i57 3

thr.wait()调用将执行以下操作:

  1. 暂停调用该Thread方法的方法!
  2. 释放任何锁定Thread

应该对完全相同的对象进行相应的notify(或)方法调用(即或notifyAllthr.notify()thr.notifyAll()Thread)方法调用。

请注意,操作侦听器actionPerformed方法是在事件调度线程(简称 EDT)(其本身就是一个Thread)上调用的。也就是说,通过点击end按钮,actionPerformed在EDT上调用它,然后你再调用thr.wait()它,这意味着你暂停了EDT!在Swing中,据我所知,几乎所有与事件相关的操作都发生在EDT上。这意味着,如果您在 EDT 上运行,那么您会阻止其他操作,例如接收按钮单击、鼠标移动和悬停等事件……简而言之,阻止 EDT 意味着 GUI 无响应。

除此之外,请thr.wait()致电(以及thr.notify()thr.notifyAll())应该在块内完成synchronized (thr) { ... }

如果您想与Thread不同于 EDT 的对象进行交互(例如通过使用Thread构造函数、 an ExecutorService、 aSwingWorker等...),并在两个Threads 之间进行通信,您通常需要某种同步(因为您有两个Threads:EDT 和创建的)。您将需要这种同步,因为两个Threads(为了进行通信)将共享[对]同一变量的引用。在你的情况下,它是print需要共享的标志;一个Thread(EDT)应根据按下的按钮修改标志,而另一个(用 类Thread的实例构造的)名为ExampleRunnablethr应在一定间隔/时间后重复读取标志,然后执行中的印刷工作System.out

另请注意,该printflag 是 class 的静态属性Example,但您需要一个类实例来Thread同步 s。所以看来你要使用Examplethr为此命名的类实例。

以下面的代码为例:

import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.SwingUtilities;

public class ThreadMain {
    
    private static class PrintingThread extends Thread {
        
        private boolean print;
        
        public PrintingThread() {
            print = false;
        }
        
        public synchronized void keepPrinting() {
            print = true;
            notifyAll();
        }
        
        public synchronized void pausePrinting() {
            print = false;
        }
        
        @Override
        public void run() {
            try {
                while (true) { //You should add an end condition here, in order to let the Thread shutdown gracefully (other than interrupting it).
                    synchronized (this) {
                        if (!print)
                            wait();
                    }
                    System.out.println("Printing...");
                    Thread.sleep(500);
                }
            }
            catch (final InterruptedException ix) {
                System.out.println("Printing interrupted.");
            }
        }
    }
    
    private static void createAndShowGUI() {
        
        final PrintingThread printingThread = new PrintingThread();
        printingThread.start();
        
        final JRadioButton start = new JRadioButton("Print"),
                           stop = new JRadioButton("Pause", true);
        
        start.addActionListener(e -> printingThread.keepPrinting());
        stop.addActionListener(e -> printingThread.pausePrinting());
        
        /*Creating a button group and adding the two JRadioButtons, means that when
        you select the one of them, the other is going to be unselected automatically.
        The ButtonGroup instance is then going to be maintained in the model of each
        one of the buttons (JRadioButtons) that belong to the group, so you don't need
        to keep a reference to group explicitly in case you worry it will get Garbadge
        Collected, because it won't.*/
        final ButtonGroup group = new ButtonGroup();
        group.add(start);
        group.add(stop);
        
        final JPanel contentsPanel = new JPanel(); //FlowLayout by default.
        contentsPanel.add(start);
        contentsPanel.add(stop);
        
        final JFrame frame = new JFrame("Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(contentsPanel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
    
    public static void main(final String[] args) {
        
        //EDT related code should be called on the EDT..
        SwingUtilities.invokeLater(ThreadMain::createAndShowGUI);
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以在此处看到,我创建了一个自定义Thread和重写的方法,用于在 500 毫秒的某个间隔/时间后run重复打印。System.out循环永远不会结束,除非Thread被中断。但不要用作您正在尝试的示例实现,因为:

  1. 它没有正常终止的条件Thread。例如,它应该有一个条件而不是truewhile循环中来指示我们何时需要退出Thread优雅地退出。
  2. Thread.sleep它在循环中调用。据我所知,这被认为是不好的做法,因为通常情况下,当您需要重复执行一项操作并依赖Thread.sleep给您一些空闲时间时,您应该使用 aScheduledExecutorService或 ajava.util.Timer来以固定速率安排所需的操作。

另请注意,此处需要同步,因为有两个Threads(EDT 和PrintingThread)。我再说一遍,因为在下一个示例中,我们将简单地利用 EDT 本身来进行打印(因为在System.out这种情况下,在单个消息中打印不会太长),这是另一个示例实现你正在尝试做的事。为了在 EDT 本身上以固定速率安排操作,我们将使用javax.swing.Timer用于此目的的 。

代码:

import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class TimerMain {
    
    private static void createAndShowGUI() {
        
        //Constructs a Timer such that, when running, every 500ms prints the desired message:
        final Timer printingTimer = new Timer(500, e -> System.out.println("Printing..."));
        
        /*The Timer is going to repeat events (ie call all its
        ActionListeners repeatedly)... This will simulate a loop.*/
        printingTimer.setRepeats(true);
        
        /*Coalescing means that events fast enough are going to be merged to one
        event only, and we don't want that in this case, so we set it to false:*/
        printingTimer.setCoalesce(false);
        
        final JRadioButton start = new JRadioButton("Print"),
                           stop = new JRadioButton("Pause", true);
        
        start.addActionListener(e -> printingTimer.restart());
        stop.addActionListener(e -> printingTimer.stop());
        
        /*Creating a button group and adding the two JRadioButtons, means that when
        you select the one of them, the other is going to be unselected automatically.
        The ButtonGroup instance is then going to be maintained in the model of each
        one of the buttons (JRadioButtons) that belong to the group, so you don't need
        to keep a reference to group explicitly in case you worry it will get Garbadge
        Collected, because it won't.*/
        final ButtonGroup group = new ButtonGroup();
        group.add(start);
        group.add(stop);
        
        final JPanel contentsPanel = new JPanel(); //FlowLayout by default.
        contentsPanel.add(start);
        contentsPanel.add(stop);
        
        final JFrame frame = new JFrame("Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(contentsPanel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
    
    public static void main(final String[] args) {
        
        //EDT related code should be called on the EDT...
        SwingUtilities.invokeLater(TimerMain::createAndShowGUI);
    }
}
Run Code Online (Sandbox Code Playgroud)

代表javax.swing.Timer循环的目的。

还要注意这里,我们没有使用synchornized关键字,因为我们不需要,因为所有代码都在 EDT 上运行。

SwingUtilities.invokeLaterRunnable只是在未来某个时刻在 EDT 上调用 a 的少数方法。因此,我们还需要在 EDT 上调用JFrameJPanelJRadioButtons(或者简单地调用)的创建,因为它是 EDT 相关代码(例如,如果在将面板添加到框架时触发了事件怎么办?... createAndShowGUI.)。

我在代码中添加了一些注释,以帮助解决与所示示例相关的其他内容。

有任何问题请在评论中告诉我,我会尽快更新我的答案。