Matthias
Matthias

Reputation: 13434

Why is it so hard to stop a thread in Java?

I've come again in one of THOSE situations where it is just impossible to stop/destroy/suspend a thread. .interrupt() doesn't do the trick and .stop() and .suspend() are deprecated.

Very simple example:

public class TimerThread extends Thread {

    private JPanel colorPanel;

    public TimerThread(JPanel colorPanel) {
        this.colorPanel = colorPanel;
    }

    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
                colorPanel.repaint();
            } catch (Exception ex) {
                //do Nothing
            }
        }
    }
}

What this does is repaint a certain JPanel every second to change its colour. I want to start and stop the thread like this from another class:

timer = new Thread(new TimerThread(colorPanel));

    startButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            timer.start();
        }
    });

    stopButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            timer.interrupt();
        }
    });

Obviously (?) this doesn't work... I know I could use a Timer, a SwingWorker or declare the timer as timer = new TimerThread(colorPanel); and use a boolean instead of "true" in the run method, but I've been asked to declare timer as a "Thread" and nothing else.

To my surprise (or is this that stupid?), even this didn't work:

startButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            timer = new Thread(new TimerThread(colorPanel));
            timer.start();
        }
    });

    stopButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            timer.interrupt();
            timer = null;
        }
    });

So my question is simple: How do you make threads Start/Pause/Resume/Stop in Java?

Upvotes: 2

Views: 1616

Answers (7)

toto2
toto2

Reputation: 5326

With this solution you won't get "instantaneous" updates that you could get with wait/notify or interrupt, but if you don't mind the fraction of a second delay, it should do the job.

volatile boolean stopped = false;
volatile boolean paused = false;

pauseButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        paused = true;
    }
});

resumeButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        paused = false;
    }
});

stopButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        stopped = true;
    }
});

... TimerThread

public void run() {
    while (stopped == false) {
        try {
            Thread.sleep(1000);
            if (stopped)
               break;
            if (!paused) 
               colorPanel.repaint();
        } catch (Exception ex) {
            //do Nothing
        }
    }
}

Upvotes: 0

ratchet freak
ratchet freak

Reputation: 48216

when you get an interrupt you should start the cleanup and return a.s.a.p. (or at the very least reset the interrupted status)

while (true) {
    try {
        Thread.sleep(1000);
        colorPanel.repaint();
    } catch(InterruptedException e){//from sleep
        return;//i.e. stop
    } catch (Exception ex) {
        //do Nothing
    }
}

another way is to check Thread.interrupted() in the condition (but you'll need to reset the interrupted status in the catch of InterruptedException

however in swing you can use javax.swing.Timer to let an event run every so often and stop that with the api of that

javax.swing.Timer timer = new Timer(1000,new ActionListener() {
     public void actionPerformed(ActionEvent e) {
         colorPanel.repaint();
     }
});
timer.setRepeats(true);

startButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        timer.start();
    }
});

stopButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        timer.stop();
    }
});

Upvotes: 5

Bruno Reis
Bruno Reis

Reputation: 37832

Instead of looping while (true), you should loop while the thread is not interrupted:

@Override public void void() {
  // some kind of initialization...
  while (!Thread.currentThread().isInterrupted()) {
    try { ...
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt(); // ensure interrupt flag is set
    }
  }
  // some kind of cleanup
}

If InterruptedException is not thrown by anything inside your while block, either you don't use blocking operations (and simply calling Thread.interrupt() on this thread would stop it the next iteration) or you use some blocking calls that are not well behaved (there are many such examples in the JCL itself!).

Upvotes: 1

jlemos
jlemos

Reputation: 536

The correct way to do this is indeed to have a variable that determines when the Thread should be stopped, exiting from its run method. You can find more information about how to do this properly here

Upvotes: 0

Behrang Saeedzadeh
Behrang Saeedzadeh

Reputation: 47961

Try this:

public class TimerThread extends Thread {

    private volatile boolean stop = false;
    private JPanel colorPanel;

    public TimerThread(JPanel colorPanel) {
        this.colorPanel = colorPanel;
    }

    public void stopTimer() {
        stop = true;
    }

    public void run() {
        while (stop == false) {
            try {
                Thread.sleep(1000);
                colorPanel.repaint();
            } catch (Exception ex) {
                //do Nothing
            }
        }
    }
}

// Why new Thread(new TimerThread(...))?
// timer = new Thread(new TimerThread(colorPanel)); 

timer = new TimerThread(colorPanel)

startButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        timer.start();
    }
});

stopButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        timer.stopTimer();
    }
});

Also have a look at here to see how you can replicate stop now that it's deprecated.

Upvotes: 3

David Heffernan
David Heffernan

Reputation: 613302

It is dangerous to stop threads pre-emptively. Doing so leads to deadlocks, resource leaks and so on. Instead you should use a cooperative signaling mechanism.

Signal to the thread that you want it to stop, and then wait for it to do so. The thread should regularly check whether it needs to stop and react accordingly.

Upvotes: 1

Jon Skeet
Jon Skeet

Reputation: 1502276

You make them co-operate, basically. You have some shared flags to let them see what they should be doing, and whenever you would sleep, instead you wait on some shared monitor. Then when you want to control the thread, you set the appropriate flag and notify the monitor so that if the thread was waiting, it will wake up and notice that it should suspend/stop/whatever. Obviously you need to take the normal sort of care around shared state, using volatile variables, Atomic* objects or locking to make sure that every thread sees the updates made by every other thread.

Anything non-cooperative is risky due to the chance of corrupting state half way through an operation.

Upvotes: 1

Related Questions