Java Object wait(long timeout, int nanos) 方法(保姆级教程)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

在 Java 多线程编程中,线程间的协作与同步是核心挑战之一。Java Object wait(long timeout, int nanos) 方法作为 Object 类提供的关键方法之一,为开发者提供了精确控制线程等待时间的能力。无论是构建生产者-消费者模型,还是实现定时任务调度,这一方法都扮演着重要角色。本文将从基础概念出发,结合代码示例和实际场景,深入解析该方法的原理、用法及注意事项,帮助读者系统掌握这一技术点。


方法概述与核心作用

线程等待机制的核心

在 Java 中,线程可以通过 Object 类的 wait()notify()notifyAll() 方法实现线程间通信。其中,wait() 方法使当前线程进入等待状态,直到被其他线程唤醒或超时。而 wait(long timeout, int nanos)wait() 方法的重载版本,允许开发者以毫秒级精度控制等待时间。

方法签名

public final void wait(long timeout, int nanos) throws InterruptedException  

方法作用的比喻

可以将线程比作排队的人:

  • wait():当前线程暂时离开队列,进入“休息室”等待。
  • notify()notifyAll():相当于向休息室发送通知,唤醒特定线程或全部线程。
  • wait(timeout, nanos):设定一个“截止时间”,若在指定时间内未收到通知,则自动返回队列继续执行。

参数详解与底层逻辑

参数含义与约束

该方法接受两个参数:

  • timeout (long):等待的毫秒数(精确到秒)。
  • nanos (int):毫秒之外的纳秒部分(0-999,999,999)。

关键约束

  1. 纳秒参数必须在 0 到 999,999,999 之间,超出范围会抛出 IllegalArgumentException
  2. 必须在同步块(synchronized)中调用,否则会抛出 IllegalMonitorStateException

时间计算逻辑

方法的总等待时间为:

总时间 = timeout 毫秒 + (nanos 纳秒 / 1,000,000,000)  

例如,wait(1, 500_000_000) 表示等待 1.5 秒


使用场景与核心流程

典型应用场景

  1. 生产者-消费者模式:消费者线程在缓冲区为空时等待,直到生产者放入数据。
  2. 定时任务:线程在未收到信号时,按固定间隔检查任务状态。
  3. 资源竞争控制:避免线程频繁轮询共享资源,降低 CPU 占用。

方法调用的规范流程

使用 wait(timeout, nanos) 需遵循以下步骤:

  1. 获取对象锁:通过 synchronized 关键字锁定目标对象。
  2. 检查条件:在同步块内判断是否需要等待(如资源不足)。
  3. 调用 wait():若需等待,调用 wait(timeout, nanos) 进入等待状态。
  4. 响应唤醒或超时:等待结束后重新检查条件并执行后续操作。

代码示例与实践分析

示例 1:基础用法

public class WaitExample {  
    private static final Object lock = new Object();  
    private static boolean condition = false;  

    public static void main(String[] args) {  
        Thread waiter = new Thread(() -> {  
            synchronized (lock) {  
                System.out.println("等待开始");  
                try {  
                    lock.wait(2, 0); // 等待 2 秒或被唤醒  
                } catch (InterruptedException e) {  
                    Thread.currentThread().interrupt();  
                    System.out.println("等待被中断");  
                } finally {  
                    System.out.println("等待结束");  
                }  
            }  
        });  

        waiter.start();  

        try {  
            Thread.sleep(1000); // 主线程延迟 1 秒  
            synchronized (lock) {  
                lock.notify(); // 提前唤醒等待线程  
            }  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}  

输出结果

等待开始  
等待结束  

分析:主线程在 1 秒后调用 notify(),提前唤醒等待线程,因此实际等待时间不足 2 秒。


示例 2:生产者-消费者模型

public class ProducerConsumerExample {  
    private static final Object lock = new Object();  
    private static int count = 0;  
    private static final int CAPACITY = 10;  

    public static void main(String[] args) {  
        Thread producer = new Thread(() -> {  
            while (true) {  
                synchronized (lock) {  
                    while (count >= CAPACITY) {  
                        try {  
                            lock.wait(); // 缓冲区满时等待  
                        } catch (InterruptedException e) {  
                            Thread.currentThread().interrupt();  
                        }  
                    }  
                    count++;  
                    System.out.println("生产者生产,当前库存:" + count);  
                    lock.notify(); // 唤醒消费者  
                }  
                try {  
                    Thread.sleep(500);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        });  

        Thread consumer = new Thread(() -> {  
            while (true) {  
                synchronized (lock) {  
                    while (count <= 0) {  
                        try {  
                            lock.wait(3, 0); // 等待 3 秒后自动检查条件  
                            if (count <= 0) {  
                                System.out.println("消费者超时,放弃消费");  
                                return; // 超时后终止线程  
                            }  
                        } catch (InterruptedException e) {  
                            Thread.currentThread().interrupt();  
                        }  
                    }  
                    count--;  
                    System.out.println("消费者消费,当前库存:" + count);  
                    lock.notify(); // 唤醒生产者  
                }  
                try {  
                    Thread.sleep(1000);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        });  

        producer.start();  
        consumer.start();  
    }  
}  

关键点解析

  1. 超时机制:消费者线程在缓冲区为空时,最多等待 3 秒。若超时未被唤醒,则自动退出。
  2. 条件检查:使用 while 循环而非 if,避免虚假唤醒(False Wakeup)问题。

常见问题与注意事项

问题 1:为何必须在同步块中调用?

wait() 系列方法依赖对象锁(Monitor)。调用线程必须持有该对象的锁,否则无法安全释放锁并进入等待状态。

问题 2:如何避免死锁?

  • 正确释放锁:确保 wait() 调用在 synchronized 块内,且在调用后不会意外释放锁。
  • 合理唤醒顺序:避免多个线程互相等待对方释放资源。

问题 3:wait(timeout)wait(timeout, nanos) 的区别

wait(long timeout) 的纳秒部分默认为 0,而 wait(timeout, nanos) 允许更精确的纳秒级控制。


性能优化与替代方案

方法的局限性

  1. 精度限制:纳秒参数的精度受操作系统调度影响,实际精度可能低于纳秒级别。
  2. 中断风险:若线程在等待期间被中断,会抛出 InterruptedException,需妥善处理。

替代方案:Thread.sleep()

Thread.sleep() 是静态方法,使当前线程休眠指定时间,但无法响应其他线程的唤醒信号。

对比表格
| 方法 | 是否响应唤醒信号 | 是否需要持有锁 | 精度控制 |
|-----------------------|------------------|----------------|-------------------|
| wait(timeout, nanos)| 是 | 是 | 毫秒+纳秒 |
| Thread.sleep() | 否 | 否 | 毫秒(整数) |


结论

通过本文的讲解,读者应能理解 Java Object wait(long timeout, int nanos) 方法的核心逻辑、使用场景及潜在风险。这一方法是构建高效多线程应用的关键工具,但需结合同步机制、异常处理和代码结构设计,才能发挥其最大价值。在实际开发中,建议结合 Condition 接口(如 java.util.concurrent.locks 包)实现更灵活的线程协作,同时持续关注线程安全与性能优化。

掌握这一方法后,开发者可以更自信地应对复杂并发场景,例如分布式任务调度、实时数据处理等,为构建高并发系统打下坚实基础。

最新发布