解决Java中的IllegalMonitorStateException问题

1,730 阅读3分钟

在本教程中,我们将 学习IllegalMonitorStateException以及它何时被抛出的例子。我们还将看到如何防止我们的代码抛出这种异常,以及在多线程应用程序中应遵循的一些最佳实践。

1.JavaIllegalMonitorStateException

IllegalMonitorStateException 类存在于java.lang 包中 ,从 Java 1.0版本开始就存在了 它扩展了 RuntimeException类;因此,它是一个未经检查的异常,不需要在方法或构造函数的throws 条款中声明。

在这个类中定义的构造函数

  • IllegalMonitorStateException(): 创建一个IllegalMonitorStateException类的实例,设置null为其消息。
  • IllegalMonitorStateException(String *message*):创建IllegalMonitorStateException类的一个实例,使用指定的消息

2.导致IllegalMonitorStateException的原因

对于线程间的通信(2个线程互相通信),线程必须使用wait(),notify()notifyAll()方法。线程类从对象类中继承了这些方法。

一个线程应该获得对象的锁来调用该对象上的这些方法。如果一个Thread试图在没有获得对象的锁或在同步块之外调用wait(), notify()notifyAll() 方法,程序会抛出IllegalMonitorStateException。**

现在让我们看一个例子,看看线程 如何引发IllegalMonitorStateException。 在这个例子中,我们将创建一个WaitingThread和一个NotifyingThread。

  • WaitingThread 调用wait() 方法并进入等待状态,直到有其他线程 调用notify() 方法并通知它。
  • NotifyingThread 调用notify() 方法,并通知等待中的Thread 重新开始处理。
public class IllegalMonitorStateExceptionDemo
{

    //This object is used for synchronization among threads.
    public final static Object obj = new Object();
    
    public static class WaitingThread extends Thread {

        @Override
        public void run() {
            try {
                   //Calling wait() method outside of synchronized area
                    obj.wait();    // Raises IllegalMonitorStateException
                 }
                catch (InterruptedException ex) {
                    System.out.println(Thread.currentThread().getName() + " gets Interrupted");
                }
        }
    }
     
    public static class NotifyingThread extends Thread {

        @Override
        public void run() {
          try {
                    // Thread sleep for 5 sec
                    Thread.sleep(5000);
                    // Calling notify() outside of synchronized area                 
                    obj.notify();         // Raises IllegalMonitorStateException
                }
                catch (InterruptedException ex) {
                    System.err.println(Thread.currentThread().getName() + " gets Interrupted");
                }
        }
    }
}

尝试运行这两个线程来验证。

public static void main(String[] args) {
    WaitingThread waitingThread = new WaitingThread();
    NotifyingThread notifyingThread  = new NotifyingThread();

    waitingThread.start();
    notifyingThread.start();
}

我们将在控制台中得到IllegalMonitorStateException

Exception in thread "Thread-0" java.lang.IllegalMonitorStateException: current thread is not owner
	at java.base/java.lang.Object.wait(Native Method)
	at java.base/java.lang.Object.wait(Object.java:338)
	at com.howtodoinjava.concurrency.IllegalMonitorStateExceptionDemo $WaitingThread.run(IllegalMonitorStateExceptionDemo.java:23)

Exception in thread "Thread-1" java.lang.IllegalMonitorStateException: current thread is not owner
	at java.base/java.lang.Object.notify(Native Method)
	at com.howtodoinjava.concurrency.IllegalMonitorStateExceptionDemo $NotifyingThread.run(IllegalMonitorStateExceptionDemo.java:39)

3.解决IllegalMonitorStateException

IllegalMonitorStateException 解决的是当一个线程 在获得一个对象的锁后调用wait(),notify()notifyAll() 方法时,意味着它应该 synchronized 块内调用这些方法

在上面的例子中,如果等待和通知线程都在同步 块中调用wait()notify() ,那么我们的代码中就不会出现IllegalMonitorStateException

因此,正确的调用这些方法而不出现这个异常的方法是如下的。

  public static class WaitingThread extends Thread {

        @Override
        public void run() {
            try {
                    //Obtain lock using the synchronized keyword
                    synchronized(obj){
                        
                       //Calling wait() inside synchronized block
                        obj.wait();
                    }
                  }
                catch (InterruptedException ex) {
                    System.err.println(Thread.currentThread().getName() + " gets Interrupted");
                }
        }
  }

public static class NotifyingThread extends Thread {

        @Override
        public void run() {
          try {
                    Thread.sleep(5000);

                    //Obtain lock first
                     synchronized(obj){  
                        //Calling notify() inside synchronized block
                        obj.notify();
                     }
                }
                catch (InterruptedException ex) {
                    System.err.println(Thread.currentThread().getName() + " gets Interrupted");
                }
        }
}

现在,如果我们运行该程序,它就会成功完成。

4.最佳实践

  • 自从Java 1.5版本以来,我们有了 java.util.concurrent包,它包含了各种新的并发框架 ,使我们在多线程环境中工作时更加轻松。
  • 对于线程间的通信,我们可以使用java.util.concurrent包中的BlockingQueue来代替使用wait()notify() 方法的老方法。
  • BlockingQueue接口代表了一个队列,它是线程安全的,可以在两个线程相互通信时放入和取出元素,这反过来也避免了任何IllegalMonitorStateException
  • 同步 块相比, 接口和ReentrantLock 类提供了对并发的更多控制。使用它,一个线程可以在不等待的情况下尝试加锁,消除了死锁 的可能性。
  • 我们可以使用其他类,如CountDownLatchCyclicBarrierExchanger、Phaser、SynchronousQueue,以便在多线程应用程序中处理多个线程。

5.总结

在这篇文章中,我们了解了IllegalMonitorStateException, 它的原因是什么以及如何在我们的代码中防止它。我们还看到了一些使用新的并发类的最佳实践,这有助于避免IllegalMonitorStateException。