How to start/resume and stop/pause a thread inside the action listener in java

The code, as provided will not print anything. It also won't compile, you need to fix private static Thread; to say private static Thread thr;.

Anyway, this can work or not, depending, as the code lacks any synchronization. This means changes made to a variable in one thread need not be visible in another. If you have a single variable set to false initially, and then set it to true in one thread, a second thread can still see it's cached value of false.

Try making your boolean variables volatile and see if it works, but a real answer is reading up on thread synchronization e.g. in the Java Tutorial


The thr.wait() call will do the following:

  1. Suspend the Thread that called the method!
  2. Release any locks the Thread (that called the method) currently holds.

The corresponding notify (or notifyAll) method call should be made for the exact same object (ie thr.notify() or thr.notifyAll()) that suspended the Thread we want to continue.

Notice that the action listener actionPerformed method is called on the Event Dispatch Thread (EDT for short) (which is itself a Thread). That is, by clicking the end button, the actionPerformed is called on the EDT and then you call thr.wait() on it, which means that you suspend the EDT! In Swing, as far as I know, almost every event related operation takes place on the EDT. That means that if you run on the EDT then you block other operations, such as receiving events from button clicks, mouse movement and hovering, etc... In short, blocking the EDT means unresponsive GUI.

Aside from that, thr.wait() call (as well as thr.notify() and thr.notifyAll()) should be done inside a synchronized (thr) { ... } block.

If you want to interact with a Thread different than the EDT (such as by using the Thread constructors, an ExecutorService, a SwingWorker etc...), and also make a communication between the two Threads, you usually need some kind of synchronization (because you have two Threads: the EDT and the one created). You will need this synchronization because the two Threads (in order to communicate) are going to share [a reference to] the same variable. In your case it's the print flag which needs to be shared; one Thread (the EDT) shall modify the flag, according to what button was pressed, while the other Thread (the one constructed with an instance of the class Example which is the Runnable) named thr, shall read the flag repeatedly after some interval/time and then do the work of printing in System.out.

Notice also, that the print flag is a static property of the class Example, but you need a class instance for the Threads to synchornize on. So it seems like you were going to use the Example class instance named thr for this.

Take for example the following code:

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

You can see here that I created a custom Thread and overrided run method to repeatedly print on System.out after some interval/time of 500ms. The loop will never end, unless the Thread is interrupted. Not to be used as a good example implementation of what you are trying though, because:

  1. It doesn't have a condition for normal termination of the Thread. It should have for example a condition instead of true in the while loop to indicate when we are needed to exit the Thread gracefully.
  2. It calls Thread.sleep in the loop. This is considered bad practice as far as I know, because this is the case usually when you need to do an operation repeatedly and rely on Thread.sleep to give you some spare time, when instead you should have used a ScheduledExecutorService or a java.util.Timer to schedule at fixed rate the desired operation.

Also note that you need synchornization here because you have two Threads (the EDT and the PrintingThread). I'm saying this again because in the next example we are going to simply utilize the EDT itself to do the printing (because printing in System.out a single message is not going to be too long in this case), which is another sample implementation of what you are trying to do. To schedule the operation at a fixed rate on the EDT itself, we are going to use the javax.swing.Timer which exists for such a purpose.

The code:

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

The javax.swing.Timer delegates the purpose of the loop.

Also notice here, we didn't use the synchornized keyword, because we didn't need to, because all the code runs on the EDT.

SwingUtilities.invokeLater is just a handful method to invoke a Runnable on the EDT at some point in the future. So we also need to invoke the creation of the JFrame, the JPanel and the JRadioButtons (or simply call the createAndShowGUI) on the EDT, because it is EDT related code (for example what if an event was fired while adding the panel to the frame?...).

I added some comments in the code to help out for other stuff related to the examples shown.

Let me know in the comments any questions that may arise, and I will update my answer as soon as possible.