I'm trying to learn how to pause and resume a thread in java. I'm using an Applet
that implements Runnable
has 2 buttons "Start" and "Stop".
public void init(){
th = new Thread(this);
th.start();
btn_increment = new Button("Start");
btn_increment.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent ev){
th.notify();
}
});
add(btn_increment);
btn_decrement = new Button("Stop");
btn_decrement.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent ev){
try{
th.wait();
} catch(InterruptedException e) {
e.printStackTrace();
}
}
});
add(btn_decrement);
}
The run method of the thread:
public void run(){
while(true){
repaint();
try{
Thread.sleep(20);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
Now whenever I try to to pause or resume the thread an exception is thrown:
Exception in thread "AWT-EventQueue-1" java.lang.IllegalMonitorStateException
Notes:
The previous code runs perfectly if I use the deprecated methods suspend()
and resume()
, but the documentation points out at using notify()
and wait()
instead with synchronization. I tried adding the word synchronized
to the actionPerformed
method, but it still throws the exception.
Can someone please explain WHY this isn't working and how to solve the synchronization problem? Few explanation points would really be of much help ;)
You have misunderstood how wait()
works. Calling wait
on a Thread
object does not pause that thread; it instead tells the currently running thread to wait for something else to happen. To explain why, I'll need to back up a bit and explain what synchronized
actually does.
When you enter a synchronized
block you obtain the monitor associated with an object. For example,
synchronized(foo) {
obtains the monitor associated with the object foo
.
Once you have the monitor, no other threads can obtain it until you exit the synchronized block. This is where wait
and notify
come in.
wait
is a method on the Object class that tells the currently running thread to temporarily release the monitor it holds. This allows other threads to synchronize on foo
.
foo.wait();
This thread will not resume until someone else calls notify
or notifyAll
on foo
(or the thread is interrupted). Once that happens, this thread will attempt to re-acquire the monitor for foo
and then continue. Note that if any other threads are waiting to obtain the monitor then they might get in first - there is no guarantee of the order the JVM will hand out locks. Note that wait()
will wait forever if no-one calls notify
or notifyAll
. It's usually best to use the other form of wait
that takes a timeout. That version will wake up when someone calls notify
/notifyAll
or when the timeout has expired.
So, you need one thread to do the waiting and a different thread to do the notifying. Both wait
and notify
must hold the monitor on the object they are trying to wait on or notify; this is why you are seeing the IllegalMonitorStateException.
An example might help you understand:
class RepaintScheduler implements Runnable {
private boolean paused = false;
private final Object LOCK = new Object();
public void run() {
while (true) {
synchronized(LOCK) {
if (paused) {
try {
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
repaint();
}
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void pause() {
synchronized(LOCK) {
paused = true;
LOCK.notifyAll();
}
}
public void resume() {
synchronized(LOCK) {
paused = false;
LOCK.notifyAll();
}
}
}
Your Applet code can then do this:
public void init() {
RepaintScheduler scheduler = new RepaintScheduler();
// Add listeners that call scheduler.pause and scheduler.resume
btn_increment.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {
scheduler.resume();
}});
btn_decrement.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {
scheduler.pause();
}});
// Now start everything up
Thread t = new Thread(scheduler);
t.start();
}
Note that the Applet class does not care about how the scheduler pauses/resumes and does not have any synchronized blocks.
So a possible sequence of events here is:
- Thread A starts running the repaint scheduler.
- Thread A goes to sleep for 20ms.
- Thread B (the event dispatch thread) receives a button click; calls 'pause'.
- Thread B obtains the monitor on LOCK.
- Thread B updates the 'paused' variable and calls LOCK.notifyAll.
- No threads are waiting on LOCK so nothing interesting happens.
- Thread B releases the monitor on LOCK.
- Thread A wakes up, goes through its loop again.
- Thread A obtains the monitor on LOCK.
- Thread A sees that it should be paused, so it calls LOCK.wait.
- At this point Thread A suspends, waiting for someone to call notifyAll. Thread A releases the monitor on LOCK.
- Some time later, the user clicks 'resume'.
- Thread B calls scheduler.resume.
- Thread B obtains the monitor on LOCK.
- Thread B updates the 'paused' variable and calls LOCK.notifyAll.
- Thread A sees the 'notifyAll' and wakes up. It tries to obtain the monitor on LOCK but it is held by Thread B so Thread A blocks.
- Thread B releases the monitor on LOCK.
- Thread A obtains the monitor and carries on.
Does that all make sense?
Having a separate LOCK variable is not required; I've done that to highlight the fact that you are not calling wait/notify on a Thread
instance. Similarly, the logic inside the RepaintScheduler is not ideal but is just there to illustrate how wait/notify could be used.
You can't just call notify
and wait
. You have to wait for something. And before calling notify
, you have to make it so that there's nothing to wait for anymore.
If your blocks aren't already synchronized, then something is wrong in your design.
How can you call wait
unless you have something to wait for? And how can you know that there is something to wait for if you haven't checked? And how can you check without synchronizing with the code that controls whether that thing has happened yet or not?
How can you call notify
unless something just happened that you need to notify the thread about? And how could something have happened that another thread cares about if you don't hold the lock that would tell that thread about it?
You should use wait
like this:
while (something_to_wait_for()) wait();
And that something_to_wait_for
should check something that is protected by synchronization. And you can't make something_to_wait_for
synchronized because then you have a race condition -- what if that something happens after something_to_wait_for
returns but before you enter wait
? Then you are waiting for something that already happened! So you need synchronization fundamentally. If you are just adding it at the end, your design is broken.
The solution in your case is probably to add something to wait for. Perhaps a simple boolean variable is all you need. Then your code can be while (should_wait) wait();
, should_wait = true;
, and should_wait = false(); notifyAll()
. You'll need synchronized
to protect the boolean and the wait
/notify
logic.
I think you have to synchronize on the thread in order to call wait and notify. Try to use
synchronized (th) {
th.notify();
}
and the same with wait()
.