使用synchronized但从结果IllegalMonitorStateException通知来自另一个类的线程

时间:2016-08-28 04:48:07

标签: java multithreading wait synchronized notify

我想通过多线程模拟比赛,我希望跑步者在Referee发射手枪后开始跑步,所以我把wait()放在跑步者的跑步方法中,以便等待裁判,这是跑步者(PrintRunner.class)跑步方法

@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!!!");
}

这是Referee运行方法:

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

当裁判通知选手时,我得到IllegalMonitorStateException,当我使用wait()notifyAll()时,我获得了PrintRunner锁。

请告诉我代码出错的原因。

1 个答案:

答案 0 :(得分:4)

您的程序不起作用,因为您在notifyAll()实例上调用Referee,但是在PrintRunner.class内同步的块内执行此操作。您只能在持有锁定的对象上调用notify()/notifyAll(),否则您将获得IllegalMonitorStateException

但是切换到PrintRunner.class.notifyAll()对你没有帮助,因为这个调用只会对等待PrintRunner.class对象通知的那些线程产生影响而你没有这样的线程。您的线程正在等待特定实例,而不是类本身。因此,您的Referee需要遍历所有等待的PrintRunner个实例,并在每个实例上调用notify()

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

所描述的解决方案将起作用,但它的缺点是对所有跑步者都不公平。其中一些将比其他人更早得到通知。

重要说明:使用PrintRunner.class.wait()PrintRunner.class.notifyAll()会有效,但会出现同样的不公平问题,因为每个参赛者必须重新获取单PrintRunner.class个实例上的锁才能生成进展。他们只能顺序完成。在您的情况下(除了wait()调用之外,您在同步块中没有任何内容),启动之间的延迟几乎可以忽略不计,但它仍然存在。

幸运的是,Java为您的问题提供了更好的解决方案 - CountDownLatch类。特别是,您需要一个值为CountDownLatch的值。所有跑步者必须等待此锁定,直到裁判将其设置为零。此时他们将立刻被释放。请注意,所有对象(裁判和跑步者)必须使用相同的共享锁存器。让裁判拥有它(这是手枪):

<强> 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
    }
}

为了确保所有跑步者线程都已启动并准备就绪,您可以使用另一个锁存器,其初始值等于线程数。在调用getStartLatch().await()之前,每个跑步者线程必须在准备就绪时立即计算此锁定。在倒数开始锁存器之前,裁判必须等待此锁存器。这将保证您的比赛尽可能公平 - 所有参赛者都有时间准备比赛,并且所有比赛都在同一时间发布。