Michael Ouyang Michael Ouyang - 3 months ago 12
Java Question

Notify Thread from another class using synchronized but result IllegalMonitorStateException

I want to simulate the race game by multi-thread, and I want runners to start running after

Referee
fire a pistol, so I put
wait()
in runner's run method so as to wait for the Referee, this is runner (
PrintRunner.class
) run method

@Override
public void run() {
sleepTime = random.nextInt(3)+8;
System.out.println(name+" is ready~");
try {
synchronized(this){
wait();
}
System.out.println("Start running~");
Thread.sleep(sleepTime*1000);
} catch (InterruptedException e) {
System.err.println(e.getMessage());
}
System.out.println(name +" win the game!!!");
}


And this is
Referee
run method:

@Override
public void run() {
TimeUnit unit = TimeUnit.SECONDS;
try {
unit.sleep(3);
System.out.println(name+" : On your mark, get set~");
unit.sleep(5);
} catch (InterruptedException e) {
System.err.println(e.getMessage());
}
System.out.println("Fire a pistol!!!");

synchronized(PrintRunner.class){
notifyAll();
}
}


I get an
IllegalMonitorStateException
when Referee notifies runners, I had got the PrintRunner lock when I use
wait()
and
notifyAll()
.

Please tell me why the code goes wrong.

Answer

Your program doesn't work because you're calling notifyAll() on the Referee instance, but do it inside a block, synchronized on PrintRunner.class. You can only call notify()/notifyAll() on objects on which you hold the lock, otherwise you get IllegalMonitorStateException.

But switching to PrintRunner.class.notifyAll() won't help you, because this call will only have effect on those threads that are waiting for a notification on PrintRunner.class object and you have no such threads. Your threads are waiting on particular instances, not on the class itself. As the result, your Referee needs to iterate through all the waiting PrintRunner instances and call notify() on each of them:

for(PrintRunner runner: runners) {
    synchronized(runner) {
        runner.notify();
    }
}

The described solution will work, but it has the disadvantage of not being fair for all the runners. Some of them will be notified earlier than others.

Important note: using PrintRunner.class.wait() and PrintRunner.class.notifyAll() will work, but will have the same unfairness issue because every runner will have to reacquire the lock on the single PrintRunner.class instance in order to make progress. And they can only do it sequentially. In your case (you have nothing in the synchronized block except the wait() call) the delay between starts will be pretty negligible, but still it will exist.


Luckily, Java has a better solution for your problem – the CountDownLatch class. Particularly, you need a CountDownLatch with value 1. All your runners must wait on this latch until the referee sets it to zero. At this moment they all will be released at once. Note that all the objects (the referee and the runners) must use the same shared latch. Let the referee own it (it's the pistol):

Referee.java

private final CountDownLatch startLatch = new CountDownLatch(1);

public CountDownLatch getStartLatch() {
    return startLatch;
}

@Override
public void run() {
    // prepare to start, make sure all the runners are ready
    // ...

    // This will release all the waiting runners:
    startLatch.countDown();
}

PrintRunner.java

@Override
public void run() {
    try {
        // This call will wait until the referee counts the latch down to zero
        referee.getStartLatch().await();
    } catch (InterruptedException e) {
        // Handle unexpected interruption here
    }
}

In order to make sure that all the runner threads are started and ready you can use another latch with the initial value that equals the number of threads. Each runner thread must count this latch down as soon as it's ready, right before calling getStartLatch().await(). The referee must wait on this latch before counting down the start latch. This will guarantee that your race is fair as much as possible – all the runners have time to prepare for it and all of them are released at the same time.