尽管已同步(baton),baton.notifyAll仍会提供java.lang.IllegalMonitorStateException

时间:2015-09-01 14:33:12

标签: java multithreading

我是多线程的新手,对不起小问题感到抱歉。我在下面的代码中找不到有什么问题。我正在

0-Exception in thread "Thread-0" java.lang.IllegalMonitorStateException

我想要实现的目标

我正在尝试创建一个工作线程数组,每个工作线程都会打印为该特定Thread对象设置的值。我希望线程等待轮到他们然后他们将执行代码并更新baton的值(我怀疑我做错了),然后通知其他线程,以便循环中的下一个线程然后会重复这个过程。

这是我的代码。谢谢!

package test;

public class NThread
{
  public static Integer baton;

  public static void main(String[] args) throws InterruptedException
  {
    baton = 0;
    TestUtil.N = 2;
    runThread();
  }

  protected static void runThread() throws InterruptedException
  {
    int i;
    ThreadB b[] = new ThreadB[TestUtil.N];
    for (i = 0; i < TestUtil.N; i++)
    {
      b[i] = new ThreadB();
      b[i].val = i;
      b[i].start();
    }
  }
}

class ThreadB extends Thread
{
  public int val;

  @Override
  public void run()
  {
    synchronized (NThread.baton)
    {
      while (true)
      {
        if (NThread.baton != val)
        {
          try
          {
            NThread.baton.wait();
          }
          catch (InterruptedException e)
          {
            e.printStackTrace();
          }
        }
        else
        {
          TestUtil.printNum(val);
          NThread.baton = (NThread.baton+1) % TestUtil.N;
          NThread.baton.notifyAll();
        }
      }
    }
  }
}

2 个答案:

答案 0 :(得分:3)

您执行baton,但随后在该同步部分中,您使用NThread.baton = (NThread.baton+1) % TestUtil.N;更改了baton对象引用。由于您现在在baton.notifyAll()中有一个新的对象引用,因此您不再对其进行锁定,因此当您下次调用IllegalMonitorStateException时,它位于您尚未同步的对象上 - 因此您的{{ 1}}。

要解决这个问题,你需要从你的触发器(你的final)拆分同步对象(并使用baton使其永久不变 - 这始终是一个很好的规则)。即拥有一个static final Object monitor = new Object();您可以同步,等待和通知,并保留baton作为您的数字触发器。

轻微更新的样本是:

class ThreadB implements Runnable {
    public final int val;
    public ThreadB(int val) { this.val = val; }

    @Override public void run() {
        try {
            //  synchronize outside the loop so we don't constantly lock/unlock
            synchronized (NThread.monitor) {
                while (true) { // spin until interrupted
                    while (NThread.baton != val) // handle spurious wake-ups
                        NThread.monitor.wait();
                    //  print, increment and notify
                    TestUtil.printNum(val);
                    NThread.baton = (NThread.baton + 1) % TestUtil.N;
                    NThread.monitor.notifyAll();
                }
            }
        } catch (InterruptedException e) {
            // if interrupted then we exit
        }
    }
}

使用:

运行
public class NThread {
    public static int baton;
    public static final Object monitor = new Object();

    public static void main(String[] args) throws InterruptedException {
        baton = 0;
        TestUtil.N = 2;
        runThread();
    }

    protected static void runThread() throws InterruptedException {
        int i;
        Thread b[] = new Thread[TestUtil.N];
        for (i = 0; i < b.length; i++) { // loop limit is on the array length - its clearer like that
            b[i] = new Thread(new ThreadB(i));
            b[i].start();
        }
        TimeUnit.SECONDS.sleep(1);
        for (i = 0; i < b.length; i++) b[i].interrupt();
        for (i = 0; i < b.length; i++) b[i].join();
        System.out.println("All done");
    }
}

这通常会经历更多的重构,例如将公共监视器,接力棒和参与者数量注入Runnable的构造函数以防止将这些字段公开(通常使用某种自定义类来包含它们全部)。我没有那么远,所以你可以看到原始代码的链接。

作为单独的脚注,最好不要覆盖Threadrun,而是将操作从线程对象中分离出来,这样做{{1}然后实现ThreadB,然后将其提供给Runnable的构造函数。

答案 1 :(得分:1)

您必须了解synchronized不对您正在存储值的字段应用锁定,而是对存储在该字段中的对象应用锁定。

要执行waitnotifyAll,您需要对正在执行此操作的对象进行监视锁定。我认为你注意到了这个问题,因为你使用的是拳击课Integer而不是int。你这样做是因为synchronized抱怨它无法锁定值类型。在增量期间,创建新的拳击对象。这个新对象不再被synchronized块覆盖。

在您的情况下,我认为最好的解决方案是使用可用于锁定的实用程序对象。 Object的任何简单实例都可以。