如何在Java中使用wait和notify而不出现IllegalMonitorStateException错误?

132
我有2个矩阵需要相乘并打印出每个单元的结果。一旦一个单元准备好,我需要立即打印它,但例如需要在打印[2][0]之前先打印[0][0],即使[2][0]的结果先准备好了。因此,我需要按顺序打印它们。
我的想法是让打印线程等待,直到multiplyThread通知它可以打印正确的单元为止,然后printerThread将打印单元并继续等待,以此类推。
因此,我有一个执行乘法的线程:
public void run() 
{
    int countNumOfActions = 0; // How many multiplications have we done
    int maxActions = randomize(); // Maximum number of actions allowed

    for (int i = 0; i < size; i++)
    {       
        result[rowNum][colNum] = result[rowNum][colNum] + row[i] * col[i];
        countNumOfActions++;
        // Reached the number of allowed actions
        if (countNumOfActions >= maxActions)
        {
            countNumOfActions = 0;
            maxActions = randomize();
            yield();
        }   
    }
    isFinished[rowNum][colNum] = true;
    notify();
}

打印每个单元格结果的线程:

public void run()
{
    int j = 0; // Columns counter
    int i = 0; // Rows counter
    System.out.println("The result matrix of the multiplication is:");

    while (i < creator.getmThreads().length)
    {
        synchronized (this)
        {
            try 
            {
                this.wait();
            } 
            catch (InterruptedException e1) 
            {
            }
        }
        if (creator.getmThreads()[i][j].getIsFinished()[i][j] == true)
        {
            if (j < creator.getmThreads()[i].length)
            {
                System.out.print(creator.getResult()[i][j] + " ");
                j++;
            }
            else
            {
                System.out.println();
                j = 0;
                i++;
                System.out.print(creator.getResult()[i][j] + " ");
            }
        }
    }

现在它抛出了这些异常:

Exception in thread "Thread-9" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-6" Exception in thread "Thread-4" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-5" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-8" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-7" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-11" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-10" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-12" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)

multiplyThread函数的第49行是"notify()"语句,我认为我需要以不同的方式使用同步,但我不确定如何操作。

如果有人能帮忙让这段代码正常运行,我将不胜感激。

12个回答

221

要调用 notify() 方法,你需要使用同一个对象进行同步。

synchronized (someObject) {
    someObject.wait();
}

/* different thread / object */
synchronized (someObject) {
    someObject.notify();
}

29
while(!JobCompleted); 这个选项通常是不明智的,因为它会在不断检查相同的变量时将您的CPU占用率绑定在100%(请参见此处)。 - Matt Lyons-Wood
5
“while(!JobCompleted) Thread.sleep(5);”这段代码没有那个问题。 (翻译说明:尽可能贴近原文的意思,同时加入了通俗易懂的翻译) - BeniBela
15
它仍然存在一个完全不同的问题,就是轮询(重复检查某个条件是否满足,也就是你正在做的事情)通常不如在条件改变时被通知(也就是我在答案中概述的内容)那样被优先选择。 - Bombe
3
当当前线程持有调用wait方法的对象的锁时,您可以随时调用wait。使用synchronized块或同步方法完全取决于您自己。 - Bombe
1
@BeniBela 但可以肯定的是它会更慢一些(关于Huseyin的原始问题)。 - Thomas
显示剩余4条评论

64

在 Java 中使用 waitnotifynotifyAll 方法时需要记住以下几点:

  1. 如果你期望有多个线程等待锁,应该使用 notifyAll 而不是 notify
  2. 必须在同步上下文中调用 waitnotify 方法。详细解释请参见链接。
  3. 始终在循环中调用 wait() 方法,因为如果多个线程正在等待锁并且其中一个获得了锁并重置了条件,则其他线程在唤醒后需要检查条件以查看它们是否需要再次等待或可以开始处理。
  4. 使用相同的对象调用 wait()notify() 方法;每个对象都有其自己的锁,因此在对象 A 上调用 wait() 并在对象 B 上调用 notify() 是没有任何意义的。

21

你需要使用线程吗?我想知道你的矩阵有多大,是否有一个线程做乘法,另一个线程打印会更加有效。

在进行相对复杂的线程工作之前,也许先测量一下这个时间是否值得。

如果你需要使用线程,我建议创建'n'个线程来执行单元格的乘法(也许'n'是你可用的核心数),然后使用ExecutorServiceFuture机制同时调度多个乘法操作。那样你可以根据核心数优化工作,并且使用更高级别的Java线程工具(这应该会使生活更轻松)。将结果写回接收矩阵,然后在所有Future任务完成后简单地打印它。


1
+1 @Greg 我认为你应该看一下java.util.concurrent包,正如Brian所指出的那样。 - ATorras
1
+1 还有,你也可以看看这本书,它会教你正确使用 wait() 和 notify() 的方法。http://jcip.net/ - Chii

14
假设您有一个名为BlackBoxClass的类,其中包含一个名为doSomething()的方法,并且这是一个“黑盒子”应用程序。
此外,您还有一个名为onResponse(String resp)的观察者或侦听器,它将在未知时间后被BlackBoxClass调用。
流程很简单:
private String mResponse = null; 
 ...
BlackBoxClass bbc = new BlackBoxClass();
   bbc.doSomething();
...
@override
public void onResponse(String resp){        
      mResponse = resp;       
}

假设我们不知道`BlackBoxClass`正在发生什么以及何时应该得到答案,但您不想在获得答案或换句话说获得`onResponse`调用之前继续执行代码。这时就可以使用“同步助手”:
public class SyncronizeObj {
public void doWait(long l){
    synchronized(this){
        try {
            this.wait(l);
        } catch(InterruptedException e) {
        }
    }
}

public void doNotify() {
    synchronized(this) {
        this.notify();
    }
}

public void doWait() {
    synchronized(this){
        try {
            this.wait();
        } catch(InterruptedException e) {
        }
    }
}
}

现在我们可以实现我们想要的内容:
public class Demo {

private String mResponse = null; 
 ...
SyncronizeObj sync = new SyncronizeObj();

public void impl(){

BlackBoxClass bbc = new BlackBoxClass();
   bbc.doSomething();

   if(mResponse == null){
      sync.doWait();
    }

/** at this momoent you sure that you got response from  BlackBoxClass because
  onResponse method released your 'wait'. In other cases if you don't want wait too      
  long (for example wait data from socket) you can use doWait(time) 
*/ 
...

}


@override
public void onResponse(String resp){        
      mResponse = resp;
      sync.doNotify();       
   }

}

7
您只能在拥有其监视器的对象上调用notify。因此,您需要像这样的东西:
synchronized(threadObject)
{
   threadObject.notify();
}

6

notify()也需要同步


3

我将给您展示一个简单的例子,展示在Java中正确使用waitnotify的方法。 所以我会创建两个类,分别命名为ThreadAThreadB。 ThreadA将调用ThreadB。

public class ThreadA {
    public static void main(String[] args){
        ThreadB b = new ThreadB();//<----Create Instance for seconde class
        b.start();//<--------------------Launch thread

        synchronized(b){
            try{
                System.out.println("Waiting for b to complete...");
                b.wait();//<-------------WAIT until the finish thread for class B finish
            }catch(InterruptedException e){
                e.printStackTrace();
            }

            System.out.println("Total is: " + b.total);
        }
    }
} 

对于ThreadB类:

class ThreadB extends Thread{
    int total;
    @Override
    public void run(){
        synchronized(this){
            for(int i=0; i<100 ; i++){
                total += i;
            }
            notify();//<----------------Notify the class wich wait until my    finish 
//and tell that I'm finish
            }
        }
    }

3

如果您想要简单使用如何交替执行线程:

public class MyThread {
    public static void main(String[] args) {
        final Object lock = new Object();
        new Thread(() -> {
            try {
                synchronized (lock) {
                    for (int i = 0; i <= 5; i++) {
                        System.out.println(Thread.currentThread().getName() + ":" + "A");
                        lock.notify();
                        lock.wait();
                    }
                }
            } catch (Exception e) {}
        }, "T1").start();

        new Thread(() -> {
            try {
                synchronized (lock) {
                    for (int i = 0; i <= 5; i++) {
                        System.out.println(Thread.currentThread().getName() + ":" + "B");
                        lock.notify();
                        lock.wait();
                    }
                }
            } catch (Exception e) {}
        }, "T2").start();
    }
}

response :-

T1:A
T2:B
T1:A
T2:B
T1:A
T2:B
T1:A
T2:B
T1:A
T2:B
T1:A
T2:B

当我需要同步执行4个操作时,这个程序是如何工作的? - saksham agarwal

2
我们可以调用notify来恢复等待对象的执行。
public synchronized void guardedJoy() {
    // This guard only loops once for each special event, which may not
    // be the event we're waiting for.
    while(!joy) {
        try {
            wait();
        } catch (InterruptedException e) {}
    }
    System.out.println("Joy and efficiency have been achieved!");
}

通过在同一类的另一个对象上调用notify方法来恢复此操作。
public synchronized notifyJoy() {
    joy = true;
    notifyAll();
}

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接