我是多线程的新手,对不起小问题感到抱歉。我在下面的代码中找不到有什么问题。我正在
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();
}
}
}
}
}
答案 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的构造函数以防止将这些字段公开(通常使用某种自定义类来包含它们全部)。我没有那么远,所以你可以看到原始代码的链接。
作为单独的脚注,最好不要覆盖Thread
和run
,而是将操作从线程对象中分离出来,这样做{{1}然后实现ThreadB
,然后将其提供给Runnable
的构造函数。
答案 1 :(得分:1)
您必须了解synchronized
不对您正在存储值的字段应用锁定,而是对存储在该字段中的对象应用锁定。
要执行wait
或notifyAll
,您需要对正在执行此操作的对象进行监视锁定。我认为你注意到了这个问题,因为你使用的是拳击课Integer
而不是int
。你这样做是因为synchronized
抱怨它无法锁定值类型。在增量期间,创建新的拳击对象。这个新对象不再被synchronized
块覆盖。
在您的情况下,我认为最好的解决方案是使用可用于锁定的实用程序对象。 Object
的任何简单实例都可以。