首页 > Java > java教程 > 正文

Java并发编程:wait()方法与监视器锁的正确关联

霞舞
发布: 2025-10-06 12:41:20
原创
167人浏览过

Java并发编程:wait()方法与监视器锁的正确关联

本文深入探讨了Java并发编程中wait()方法的使用规范,特别是它与对象监视器锁的紧密关联。通过分析IllegalMonitorStateException的常见成因,阐明了线程必须持有目标对象的监视器锁才能成功调用其wait()方法。文章通过示例代码演示了错误用法,并提供了正确的实现方式,旨在帮助开发者避免此类并发错误,确保多线程程序的稳定性和正确性。

理解Java中的wait()、notify()和监视器锁

java并发编程中,wait()、notify()和notifyall()是object类提供的核心方法,用于线程间的协作。它们允许一个线程在特定条件不满足时暂停执行(wait()),并在条件满足时被其他线程唤醒(notify()或notifyall())。然而,这些方法的正确使用有一个严格的前提条件:调用这些方法的线程必须持有目标对象的监监视器锁(monitor lock)

监视器锁是Java实现线程同步的一种机制,通常通过synchronized关键字来获取。当一个线程进入一个synchronized方法或synchronized代码块时,它就会尝试获取指定对象的监视器锁。如果锁已被其他线程持有,当前线程就会阻塞,直到获取到锁为止。

IllegalMonitorStateException的根源

当一个线程尝试调用一个对象的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。

豆包AI编程
豆包AI编程

豆包推出的AI编程助手

豆包AI编程483
查看详情 豆包AI编程

wait()方法的正确使用姿势

要正确使用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()来唤醒等待的线程。

注意事项与最佳实践

  1. 锁定对象的一致性: wait()、notify()和notifyAll()必须在线程持有其监视器锁的对象上调用。这意味着它们必须出现在synchronized方法内部,或者synchronized(object)代码块内部,并且object必须是调用wait()等方法的对象。
  2. 使用while循环检查条件: 线程被notify()或notifyAll()唤醒后,并不意味着它等待的条件就一定满足。存在“虚假唤醒”(spurious wakeup)的可能性,或者在被唤醒后,其他线程可能已经改变了条件。因此,总是应该在一个while循环中检查条件,而不是if语句,以确保在条件真正满足时才继续执行。
  3. notify() vs notifyAll():
    • notify():随机唤醒一个等待在目标对象监视器上的线程。如果多个线程等待相同的条件,只有一个会被唤醒,可能导致其他线程无限期等待。
    • notifyAll():唤醒所有等待在目标对象监视器上的线程。通常更安全,因为它可以确保所有相关线程都有机会重新检查条件。在许多生产者-消费者或资源池场景中,notifyAll()是更好的选择。
  4. 避免死锁: 不当的同步策略可能导致死锁。在设计并发程序时,应仔细考虑锁的获取顺序和释放时机。
  5. 处理InterruptedException: wait()方法会抛出InterruptedException,这意味着当线程在等待时被中断,它将停止等待并抛出此异常。应该妥善处理此异常,通常是重新设置中断标志Thread.currentThread().interrupt(),并根据业务逻辑决定是退出还是重试。

总结

IllegalMonitorStateException是Java并发编程中一个常见的错误,它明确指出线程在没有持有监视器锁的情况下尝试调用wait()、notify()或notifyAll()。理解synchronized关键字如何获取锁,以及wait()等方法对锁的严格要求,是编写健壮、高效Java并发程序的关键。通过确保在调用这些方法时,线程持有的是正确对象的监视器锁,并遵循最佳实践,可以有效避免此类并发问题,提升程序的稳定性和可靠性。

以上就是Java并发编程:wait()方法与监视器锁的正确关联的详细内容,更多请关注php中文网其它相关文章!

编程速学教程(入门课程)
编程速学教程(入门课程)

编程怎么学习?编程怎么入门?编程在哪学?编程怎么学才快?不用担心,这里为大家提供了编程速学教程(入门课程),有需要的小伙伴保存下载就能学习啦!

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号