
在java并发编程中,wait()、notify()和notifyall()是object类提供的核心方法,用于线程间的协作。它们允许一个线程在特定条件不满足时暂停执行(wait()),并在条件满足时被其他线程唤醒(notify()或notifyall())。然而,这些方法的正确使用有一个严格的前提条件:调用这些方法的线程必须持有目标对象的监监视器锁(monitor lock)。
监视器锁是Java实现线程同步的一种机制,通常通过synchronized关键字来获取。当一个线程进入一个synchronized方法或synchronized代码块时,它就会尝试获取指定对象的监视器锁。如果锁已被其他线程持有,当前线程就会阻塞,直到获取到锁为止。
当一个线程尝试调用一个对象的wait()、notify()或notifyAll()方法,但它并未持有该对象的监视器锁时,Java虚拟机就会抛出IllegalMonitorStateException。这通常是由于对同步机制的误解或不当使用造成的。
让我们通过一个具体的例子来分析这个问题。假设我们有一个Server类,其中包含一个daten_ablegen方法,用于处理客户端数据存储。
错误示例代码:
立即进入“豆包AI人工智官网入口”;
立即学习“豆包AI人工智能在线问答入口”;
public class Server {
private boolean sicherungswunsch = false;
private int anzahlClients = 0;
private final int maxClients = 5;
// 假设Client是一个简单的POJO类,包含一个ID
static class Client {
int ID;
public Client(int id) { this.ID = id; }
}
public synchronized void daten_ablegen(Client c) throws InterruptedException {
System.out.println("Client " + c.ID + " will Daten ablegen");
// 当条件不满足时,客户端线程进入等待状态
while (sicherungswunsch || (anzahlClients >= maxClients)) {
System.out.println("Client " + c.ID + " sleeps.");
// 错误:在此处调用了客户端对象c的wait()方法
c.wait();
}
anzahlClients++;
System.out.println("当前有 " + anzahlClients + " Clients 正在处理数据。");
// ... 执行数据存储逻辑 ...
}
public static void main(String[] args) {
Server server = new Server();
// 模拟多个客户端并发调用daten_ablegen
for (int i = 0; i < 3; i++) {
final int clientId = i;
new Thread(() -> {
try {
server.daten_ablegen(new Client(clientId));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Client " + clientId + " interrupted.");
}
}).start();
}
}
}在上述代码中,daten_ablegen方法被声明为synchronized。这意味着当一个线程调用此方法时,它会获取Server对象实例(即this)的监视器锁。然而,在while循环内部,代码尝试调用c.wait(),其中c是一个Client对象。
问题在于:当前线程持有的是Server对象的锁,而不是Client对象c的锁。根据wait()方法的使用规则,线程必须持有调用wait()方法的对象的锁。因此,当条件不满足,并且有多个线程尝试调用c.wait()时,就会抛出IllegalMonitorStateException。
要正确使用wait()方法,关键在于确保调用wait()的线程持有的是同一个对象的监视器锁。在我们的示例中,由于daten_ablegen方法是synchronized的,它锁定了Server对象(this)。因此,wait()方法也应该在Server对象上调用。
修正后的示例代码:
public class Server {
private boolean sicherungswunsch = false;
private int anzahlClients = 0;
private final int maxClients = 2; // 调整maxClients以便更容易触发等待
static class Client {
int ID;
public Client(int id) { this.ID = id; }
}
public synchronized void daten_ablegen(Client c) throws InterruptedException {
System.out.println("Client " + c.ID + " will Daten ablegen");
// 线程必须持有调用wait()方法的对象的监视器锁。
// 在此方法中,synchronized关键字锁定的是当前对象实例(即this)。
// 因此,wait()方法也必须在当前对象实例上调用。
while (sicherungswunsch || (anzahlClients >= maxClients)) {
System.out.println("Client " + c.ID + " sleeps, waiting for Server resources.");
this.wait(); // 正确:在当前Server对象上调用wait()
// 或者直接 wait(); 因为在非静态方法中,wait() 默认就是 this.wait()
}
anzahlClients++;
System.out.println("Client " + c.ID + " 开始处理。当前有 " + anzahlClients + " Clients 正在处理数据。");
// 模拟数据处理
Thread.sleep(1000);
anzahlClients--;
System.out.println("Client " + c.ID + " 处理完成。剩余 " + anzahlClients + " Clients 正在处理数据。");
// 处理完成后,唤醒其他等待的线程
this.notifyAll(); // 唤醒所有等待Server锁的线程
}
// 模拟一个方法来改变sicherungswunsch或maxClients,从而唤醒等待线程
public synchronized void releaseResources() {
System.out.println("Server释放资源,通知等待中的客户端。");
this.sicherungswunsch = false;
this.notifyAll(); // 唤醒所有等待Server锁的线程
}
public static void main(String[] args) {
Server server = new Server();
// 模拟多个客户端并发调用daten_ablegen
for (int i = 0; i < 5; i++) {
final int clientId = i;
new Thread(() -> {
try {
server.daten_ablegen(new Client(clientId));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Client " + clientId + " interrupted.");
}
}, "Client-Thread-" + clientId).start();
}
// 模拟一段时间后释放资源
try {
Thread.sleep(5000);
server.releaseResources();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}在修正后的代码中,我们将c.wait()改为了this.wait()。这样,当线程在daten_ablegen方法中等待时,它持有的是Server对象的锁,并且在Server对象上调用wait(),符合了wait()方法的使用规范。当Server对象的条件(如anzahlClients或sicherungswunsch)发生变化时,可以通过调用this.notify()或this.notifyAll()来唤醒等待的线程。
IllegalMonitorStateException是Java并发编程中一个常见的错误,它明确指出线程在没有持有监视器锁的情况下尝试调用wait()、notify()或notifyAll()。理解synchronized关键字如何获取锁,以及wait()等方法对锁的严格要求,是编写健壮、高效Java并发程序的关键。通过确保在调用这些方法时,线程持有的是正确对象的监视器锁,并遵循最佳实践,可以有效避免此类并发问题,提升程序的稳定性和可靠性。
编程怎么学习?编程怎么入门?编程在哪学?编程怎么学才快?不用担心,这里为大家提供了编程速学教程(入门课程),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号