0

0

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

霞舞

霞舞

发布时间:2025-10-06 12:41:20

|

197人浏览过

|

来源于php中文网

原创

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方法,用于处理客户端数据存储。

错误示例代码:

立即学习Java免费学习笔记(深入)”;

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对象。

Bandy AI
Bandy AI

全球领先的电商设计Agent

下载

问题在于:当前线程持有的是Server对象的锁,而不是Client对象c的锁。根据wait()方法的使用规则,线程必须持有调用wait()方法的对象的锁。因此,当条件不满足,并且有多个线程尝试调用c.wait()时,就会抛出IllegalMonitorStateException。

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并发程序的关键。通过确保在调用这些方法时,线程持有的是正确对象的监视器锁,并遵循最佳实践,可以有效避免此类并发问题,提升程序的稳定性和可靠性。

相关文章

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

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

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

778

2023.08.22

while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

94

2023.09.25

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

503

2023.08.10

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

166

2025.12.24

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

14

2026.01.21

C++多线程相关合集
C++多线程相关合集

本专题整合了C++多线程相关教程,阅读专题下面的的文章了解更多详细内容。

15

2026.01.21

Java 并发编程高级实践
Java 并发编程高级实践

本专题深入讲解 Java 在高并发开发中的核心技术,涵盖线程模型、Thread 与 Runnable、Lock 与 synchronized、原子类、并发容器、线程池(Executor 框架)、阻塞队列、并发工具类(CountDownLatch、Semaphore)、以及高并发系统设计中的关键策略。通过实战案例帮助学习者全面掌握构建高性能并发应用的工程能力。

87

2025.12.01

俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

141

2026.01.28

包子漫画在线官方入口大全
包子漫画在线官方入口大全

本合集汇总了包子漫画2026最新官方在线观看入口,涵盖备用域名、正版无广告链接及多端适配地址,助你畅享12700+高清漫画资源。阅读专题下面的文章了解更多详细内容。

24

2026.01.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 3万人学习

C# 教程
C# 教程

共94课时 | 7.9万人学习

Java 教程
Java 教程

共578课时 | 52.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

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