文章目录
java.lang.IllegalMonitorStateException
异常通常表示线程试图对一个对象进行解锁,但它并没有持有该对象的锁。换句话说,这个异常通常是因为同步代码块的使用不当,或者试图对一个没有被当前线程锁定的对象进行 wait()
, notify()
, 或 notifyAll()
操作。
问题分析
当你看到 IllegalMonitorStateException
时,这通常意味着你在编写多线程代码时,同步的使用方式不正确。可能的情况包括:
- 调用
wait()
,notify()
, 或notifyAll()
的线程没有持有对象的锁。 - 调用
unlock()
的线程没有先调用lock()
获得锁。 - 在
synchronized
块之外尝试解锁。
报错原因
报错的原因通常是同步代码块或方法的使用不当。比如,你可能在没有获得锁的情况下尝试释放锁,或者在错误的对象上调用 wait()
, notify()
, 或 notifyAll()
。
解决思路
解决这个问题的关键是确保:
- 只有在持有对象锁的情况下,才调用
wait()
,notify()
, 或notifyAll()
。 - 使用
Lock
接口时,确保在调用unlock()
之前先调用lock()
。 - 同步代码块应正确地包裹需要同步的代码。
代码示例1
以下是针对这三个解决思路增加的代码示例:
1. 只有在持有对象锁的情况下,才调用 wait(), notify(), 或 notifyAll()
public class SynchronizationExample {
private final Object lock = new Object();
public void waitForSignal() throws InterruptedException {
synchronized (lock) {
// 等待通知,当前线程必须持有lock对象的锁
lock.wait();
// 收到通知后继续执行
System.out.println("Received signal, continuing execution...");
}
// 退出同步块后,自动释放lock对象的锁
}
public void sendSignal() {
synchronized (lock) {
// 发送通知,当前线程必须持有lock对象的锁
lock.notify();
System.out.println("Signal sent.");
}
// 退出同步块后,自动释放lock对象的锁
}
}
2. 使用 Lock 接口时,确保在调用 unlock() 之前先调用 lock()
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private final Lock lock = new ReentrantLock();
public void doSomethingWithLock() {
lock.lock(); // 获取锁
try {
// 临界区,只有获取到锁的线程才能执行这里的代码
System.out.println("Doing something important...");
} finally {
lock.unlock(); // 释放锁,无论是否发生异常
}
}
}
3. 同步代码块应正确地包裹需要同步的代码
public class CorrectSynchronization {
private int counter = 0;
public synchronized void incrementCounter() {
// 同步方法,自动使用this作为锁对象
counter++;
System.out.println("Counter incremented to " + counter);
}
public void incrementCounterWithBlock() {
synchronized (this) {
// 同步代码块,显式指定锁对象this
counter++;
System.out.println("Counter incremented to " + counter + " in block");
}
// 退出同步代码块后,自动释放this对象的锁
}
}
在第一个示例中,waitForSignal
和 sendSignal
方法使用 synchronized
块和 wait
/notify
来同步线程。调用 wait
和 notify
的线程必须持有 lock
对象的锁。
在第二个示例中,doSomethingWithLock
方法使用 Lock
接口的 lock
和 unlock
方法来控制对临界区的访问。lock.lock()
必须在尝试访问共享资源之前调用,而 lock.unlock()
则在访问结束后调用,以释放锁。
在第三个示例中,incrementCounter
方法是一个同步方法,它使用 this
作为锁对象来同步对 counter
的访问。incrementCounterWithBlock
方法则显式地使用 synchronized
块和 this
作为锁对象来同步对 counter
的访问。这两种方式都确保了在任意时刻只有一个线程能够修改 counter
的值。
代码示例2
public class Example {
private Object lock = new Object();
public void someMethod() {
synchronized (lock) {
// ... do something ...
}
// 错误的尝试释放锁,因为当前线程不再持有lock的锁
lock.notify();
}
}
要修复这个问题,我们需要确保在调用 notify()
时,当前线程持有 lock
的锁。下面是修复后的代码示例:
public class Example {
private Object lock = new Object();
public void someMethod() {
synchronized (lock) {
// ... do something ...
// 正确的释放锁前的通知,因为当前线程持有lock的锁
lock.notify();
}
// 锁在这里被自动释放,因为离开了synchronized块
}
}
在这个修复后的例子中,notify()
调用被放在了 synchronized
块内部,这样当 notify()
被调用时,当前线程就持有 lock
的锁。当线程退出 synchronized
块时,锁会被自动释放。
记住,任何时候调用 wait()
, notify()
, 或 notifyAll()
,你都应该确保它们被包裹在一个 synchronized
块中,并且该块使用正确的对象作为锁。如果你使用 Lock
接口和它的 lock()
和 unlock()
方法,同样要确保正确的锁定和解锁顺序。