-1

I understand that Swing GUIs themselves use Threads, but I am trying to use a separate thread to run my simulation. I created a class that implements Runnable and uses a custom Thread as most simple Runnable examples do. My run() method basically runs my simulation, updating every second (which works great), but I'm now trying to implement buttons that can pause/resume the simulation. My Start button successfully starts the thread, and my Pause button successfully pauses the thread. However, when Pause is selected, the entire GUI is paused, and you can see the button as still being selected and I am unable to select any buttons or interact with the GUI at all. As soon as I call wait() on my custom thread, my entire GUI halts, even though I'm using a Thread separate from this GUI. Why does calling wait() freeze up my GUI? How can I pause just this specific Thread and not the entire GUI?

Please note that the Start button should be what makes the program resume. Here's my code for the GUI:

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class GridFrame extends JFrame {

    private static final long serialVersionUID = 2857470112009359285L;

    private MyGridPanel grid;
    private Simulation sim;

    GridFrame(Simulation sim, int w, int h, int rows, int cols) {
        this.sim = sim;
        setTitle("Simulation");
        setSize(w, h);
        grid = new MyGridPanel(w, h, rows, cols);
        add(grid, BorderLayout.CENTER);

        //Build bottom panel
        JPanel buttons = new JPanel();
        buttons.setLayout(new GridLayout(1,3));
        JButton start = new JButton("Start");
        JButton pause = new JButton("Pause");
        JButton reset = new JButton("Reset");

        start.setActionCommand("Start");
        start.addActionListener(new ButtonActionListener());
        pause.setActionCommand("Pause");
        pause.addActionListener(new ButtonActionListener());
        reset.setActionCommand("Reset");
        reset.addActionListener(new ButtonActionListener());

        buttons.add(start);
        buttons.add(pause);
        buttons.add(reset);

        add(buttons, BorderLayout.SOUTH);
    }

    public MyGridPanel getGrid(){
        return grid;
    }

    private class ButtonActionListener implements ActionListener{

        @Override
        public void actionPerformed(ActionEvent e) {
            switch(e.getActionCommand()){
            case "Start":
                System.out.println("Start");
                sim.start();
                break;
            case "Pause":
                System.out.println("Pause");
                sim.pause();
                break;
            case "Reset":
                System.out.println("Reset");
                break;
            }

        }
    }
}

And here is my Runnable:

public class Simulation implements Runnable{

    private Thread t;
    private GridFrame frame;
    private boolean paused;

    public Simulation(){
        frame = new GridFrame(this, 300, 300, 10, 10);
        frame.setVisible(true);
        paused = true;
    }

    public void start () {
        if(t == null){
            //Thread has not been created. Simulation has not started to run yet
            System.out.println("Starting thread.");
            t = new Thread(this);
            t.start();
            paused = false;
        }
        else if(paused){
            //Simulation and thread already started to run but was paused. This should use notify() to resume the thread.
            resume();
            paused = false;
        }
    }

    public void resume(){
        synchronized(t){
            t.notify();
        }
    }

    public void pause(){
        synchronized(t){
            try {
                t.wait();
                paused = true;
            } catch (InterruptedException e) {
                System.out.println("Exception when trying to pause simulation");
                e.printStackTrace();
            }
        }
    }

    @Override
    public void run() {
        while(true){
            frame.getGrid().step();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("Thread interrupted while simulation was running.");
                e.printStackTrace();
            }
        }

    }


    public static void main(String[] a) {
        Simulation s = new Simulation();
    }

}
OMGitzMidgar
  • 689
  • 2
  • 10
  • 28

1 Answers1

4

Calling wait and notify on a Thread object behaves no differently than it does on any other object. Specifically, as you've noticed, it does not send a signal to the executing thread that it should pause, but rather it will block the calling thread (your UI thread in this case) until it receives a notify message.

You will need to implement a messaging system (such as a blocking queue) from your UI thread to your background thread in order to get what you want.

Joe C
  • 15,324
  • 8
  • 38
  • 50
  • 1
    Nice answer except that "calling thread" is a more apt description than "currently executing" thread. – Solomon Slow Feb 05 '17 at 20:22
  • Fair enough. Edited accordingly. – Joe C Feb 05 '17 at 20:23
  • Why is the UI the "calling thread"? What is the difference between the calling thread and the executing thread? – OMGitzMidgar Feb 05 '17 at 21:07
  • But the UI is just calling a method that calls `t.wait()`. Is there a way that I can make my Thread object t be the Thread that calls `wait`? – OMGitzMidgar Feb 05 '17 at 21:09
  • As I mentioned, if you want `wait` to be called in your `t` thread, you will need to implement a messaging system, such as a blocking queue, between the two threads. – Joe C Feb 05 '17 at 21:11
  • 1
    @OMGitzMidgar Why call `wait()` at all? Your thread sleeps for one second every time around its loop. Why not just have it check a boolean variable every time at the top of the loop, and have it either call `frame.getGrid().step();` or not depending on the value of the flag? – Solomon Slow Feb 05 '17 at 21:14
  • Ok, thanks for bearing with me! Do you have any suggestions on how I can implement this? I tried looking at solutions like [this one](http://stackoverflow.com/questions/2536692/a-simple-scenario-using-wait-and-notify-in-java) but I'm not sure how it would work with this application. – OMGitzMidgar Feb 05 '17 at 21:15
  • 1
    @OMGitzMidgar, When your program has a dozen or more different threads that are not blocked in any way, and eight of those actually are running on eight different CPUs, which one of them is "the currently executing thread?" The name `Thread.currentThread()` comes from a time when computers with more than one CPU only existed in laboratories, and even then, it's meaning was kind of ambiguous to everybody except the programmer who maintained the scheduler code. – Solomon Slow Feb 05 '17 at 21:22
  • @jameslarge great suggestion! I should have including this in my question: Is it bad to have my Runnable/Thread continuously running even when my GUI is paused and not doing anything? Considering it is a simple simulation and I won't be doing this with multiple threads, is it ok to just have a Thread running when it doesn't need to be? – OMGitzMidgar Feb 05 '17 at 21:23
  • 1
    @OMGitzMidgar, a thread that does nothing except to poll a flag once per second and sleep() the rest of the time is extremely light weight as far as the rest of the system is concerned. – Solomon Slow Feb 05 '17 at 21:26