首页 > Java > java教程 > 正文

Java中Thread.sleep与wait区别

P粉602998670
发布: 2025-09-21 19:44:01
原创
460人浏览过
Java中Thread.sleep和wait的核心差异在于锁的处理:Thread.sleep不释放已持有的锁,仅实现线程暂停;而Object.wait会释放当前对象锁,并进入等待队列,直到被notify、超时或中断,用于线程间协作。

java中thread.sleep与wait区别

在Java中,

Thread.sleep()
登录后复制
Object.wait()
登录后复制
虽然都能让当前线程暂停,但它们在并发控制机制上有着根本性的区别
Thread.sleep()
登录后复制
仅仅是让线程“小憩”片刻,不涉及锁的释放;而
Object.wait()
登录后复制
则是线程主动“让出”它持有的锁,并进入等待状态,直到被其他线程唤醒。理解这一点,是编写健壮多线程代码的关键。

解决方案

要深入理解

Thread.sleep
登录后复制
Object.wait
登录后复制
的区别,我们得从它们各自的设计目的和对线程状态、锁机制的影响来分析。

Thread.sleep(long millis)
登录后复制
Thread.sleep()
登录后复制
是一个静态方法,它属于
Thread
登录后复制
类。当一个线程调用
Thread.sleep(long millis)
登录后复制
时,它会暂停当前线程的执行,进入“计时等待”(Timed Waiting)状态,持续指定的毫秒数。这个过程中,线程不会释放它当前持有的任何对象监视器锁(monitor lock)。这意味着如果一个线程在
synchronized
登录后复制
块中调用了
sleep()
登录后复制
,那么其他试图进入同一个
synchronized
登录后复制
块的线程仍然会被阻塞,直到
sleep()
登录后复制
时间结束,或者当前线程被中断。它的主要用途是引入一个简单的延时,比如在两次尝试之间等待,或者模拟一些耗时操作。

Object.wait()
登录后复制
(以及
wait(long millis)
登录后复制
wait(long millis, int nanos)
登录后复制
)
Object.wait()
登录后复制
Object
登录后复制
类的一个方法,这意味着任何对象都可以调用它。当一个线程在一个对象上调用
wait()
登录后复制
方法时,它会执行以下几个关键动作:

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

  1. 释放对象监视器锁: 这是
    wait()
    登录后复制
    sleep()
    登录后复制
    最本质的区别。调用
    wait()
    登录后复制
    的线程会立即释放它在当前对象上持有的锁。
  2. 进入等待状态: 线程会进入“等待”(Waiting)或“计时等待”(Timed Waiting)状态,并把自己放入该对象的等待队列中。
  3. 等待被唤醒: 线程会一直等待,直到:
    • 另一个线程在该对象上调用了
      notify()
      登录后复制
      notifyAll()
      登录后复制
      方法。
    • 如果调用的是
      wait(long millis)
      登录后复制
      ,等待时间超时。
    • 线程被中断(抛出
      InterruptedException
      登录后复制
      )。
    • 出现所谓的“虚假唤醒”(Spurious Wakeups),尽管不常见,但JLS允许发生,所以总是需要在循环中检查条件。

wait()
登录后复制
方法必须在同步块(
synchronized
登录后复制
块或方法)中调用,并且同步的对象必须是
wait()
登录后复制
被调用的那个对象。如果不是这样,会抛出
IllegalMonitorStateException
登录后复制
wait()
登录后复制
的核心作用是实现线程间的协作,即一个线程等待某个条件满足,而另一个线程负责满足这个条件并通知等待的线程。

总结核心差异: | 特性 |

Thread.sleep()
登录后复制
|
Object.wait()
登录后复制
| | :----------- | :--------------------------------------------- | :--------------------------------------------------- | | 锁的释放 | 不释放任何持有的锁 | 释放当前对象持有的锁 | | 所属类 |
Thread
登录后复制
类 (静态方法) |
Object
登录后复制
类 (实例方法) | | 使用场景 | 简单的时间延迟,暂停当前线程 | 线程间协作,等待特定条件满足后被唤醒 | | 同步要求 | 无强制要求,可以在任何地方调用 | 必须
synchronized
登录后复制
块/方法中调用,且同步对象与调用
wait()
登录后复制
的对象一致 | | 唤醒方式 | 只能由时间到期或被中断唤醒 | 可以被
notify()
登录后复制
/
notifyAll()
登录后复制
唤醒,时间到期或被中断唤醒 |

public class SleepAndWaitDifference {

    private static final Object lock = new Object();
    private static boolean conditionMet = false;

    public static void main(String[] args) throws InterruptedException {

        // 示例1: Thread.sleep 不释放锁
        Thread sleepThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + ": 获取到锁,准备sleep...");
                try {
                    Thread.sleep(2000); // 暂停2秒,但不释放锁
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                System.out.println(Thread.currentThread().getName() + ": sleep结束,释放锁。");
            }
        }, "SleepThread");

        Thread blockingThread = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ": 尝试获取锁...");
            synchronized (lock) { // 会被SleepThread阻塞
                System.out.println(Thread.currentThread().getName() + ": 成功获取到锁。");
            }
        }, "BlockingThread");

        sleepThread.start();
        // 确保SleepThread先获取锁
        Thread.sleep(100);
        blockingThread.start();

        sleepThread.join();
        blockingThread.join();
        System.out.println("--- Sleep 示例结束 ---\n");


        // 示例2: Object.wait 释放锁
        Thread waitingThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + ": 获取到锁,准备wait...");
                while (!conditionMet) { // 使用while循环防止虚假唤醒
                    try {
                        lock.wait(); // 释放锁并等待
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
                System.out.println(Thread.currentThread().getName() + ": 被唤醒,条件满足,继续执行,释放锁。");
            }
        }, "WaitingThread");

        Thread notifyingThread = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ": 准备通知...");
            try {
                Thread.sleep(1000); // 模拟一些工作
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            synchronized (lock) { // 必须获取到锁才能notify
                conditionMet = true;
                System.out.println(Thread.currentThread().getName() + ": 改变条件,并调用notifyAll()...");
                lock.notifyAll(); // 唤醒所有等待的线程
            }
        }, "NotifyingThread");

        waitingThread.start();
        Thread.sleep(100); // 确保waitingThread先进入wait状态
        notifyingThread.start();

        waitingThread.join();
        notifyingThread.join();
        System.out.println("--- Wait/Notify 示例结束 ---");
    }
}
登录后复制

运行上述代码,你会清晰地看到

BlockingThread
登录后复制
SleepThread
登录后复制
释放锁之前无法执行,而
NotifyingThread
登录后复制
可以在
WaitingThread
登录后复制
释放锁后获取锁并进行操作。

Java中Thread.sleep和wait方法在并发编程中的核心差异是什么?

最核心的差异在于它们对对象监视器锁(monitor lock)的处理方式。

Thread.sleep()
登录后复制
在暂停当前线程执行时,不会释放任何它已经持有的锁。这意味着,如果一个线程在同步块内部调用
sleep()
登录后复制
,那么它会一直持有这个锁,其他任何试图获取这个锁的线程都会被阻塞,直到
sleep()
登录后复制
结束或者当前线程被中断。这使得
sleep()
登录后复制
更适合用于简单的、不涉及资源共享竞争的延迟场景。

相比之下,

Object.wait()
登录后复制
的设计初衷就是为了解决线程间的协作问题,因此它在使当前线程进入等待状态的同时,会主动释放它持有的当前对象的锁。这个行为至关重要,因为它允许其他线程有机会获取到这个锁,进而修改共享资源的状态,并最终通过
notify()
登录后复制
notifyAll()
登录后复制
来唤醒等待的线程。这种机制是实现生产者-消费者模式、线程池等复杂并发结构的基础。

此外,

wait()
登录后复制
方法的唤醒机制也更为灵活。除了可以像
sleep()
登录后复制
那样因时间到期或被中断而唤醒外,它还能被其他线程通过
notify()
登录后复制
notifyAll()
登录后复制
方法显式唤醒,这使得它能够响应外部事件,实现条件等待。而
sleep()
登录后复制
则完全是被动的,只能等待时间流逝。

如何在实际场景中正确选择使用Thread.sleep还是Object.wait()?

选择

Thread.sleep
登录后复制
还是
Object.wait()
登录后复制
,完全取决于你的应用场景和需求:

选择

Thread.sleep()
登录后复制
的场景: 当你需要一个简单、时间驱动的暂停,并且不希望释放任何锁,或者根本没有锁需要释放时,
Thread.sleep()
登录后复制
是合适的选择。

  • 引入固定延迟: 例如,在UI动画中,你可能希望每隔一段时间更新画面;或者在重试机制中,等待一段时间后再次尝试连接。
  • 模拟耗时操作: 在测试或开发阶段,你可能需要模拟一个网络请求或数据库查询的延迟。
  • 降低CPU使用率: 在某些轮询(polling)场景中,为了避免CPU空转,可以在每次轮询之间加入短暂的
    sleep()
    登录后复制
    ,但通常有更好的并发工具来替代这种模式。
  • 无需线程间协作: 你的线程只是自己暂停一下,不依赖其他线程改变状态来继续执行。

选择

Object.wait()
登录后复制
(配合
notify()
登录后复制
/
notifyAll()
登录后复制
) 的场景:
当你需要实现线程间的协作,让一个线程等待某个特定条件满足,并且在等待期间需要释放锁,以便其他线程能够改变这个条件时,
Object.wait()
登录后复制
是不可或缺的。

  • 生产者-消费者模式: 生产者线程生产数据放入共享队列,如果队列满则
    wait()
    登录后复制
    ;消费者线程从队列取数据,如果队列空则
    wait()
    登录后复制
    。当有数据或空间时,另一方
    notify()
    登录后复制
    唤醒。
  • 等待资源可用: 一个线程需要某个资源,如果资源不可用,它就
    wait()
    登录后复制
    ,直到另一个线程释放或创建了资源并
    notify()
    登录后复制
    它。
  • 实现自定义同步器:
    BlockingQueue
    登录后复制
    CountDownLatch
    登录后复制
    等并发工具的底层实现,都离不开
    wait()
    登录后复制
    notify()
    登录后复制
    机制。
  • 条件等待: 线程需要等待某个布尔标志变为
    true
    登录后复制
    ,或者某个计数器达到特定值。

关键的决策点在于: 你暂停线程的目的是什么?是为了单纯的时间延迟,还是为了等待某个条件的改变?如果涉及到共享资源和线程间的条件依赖,并且需要释放锁以便其他线程能够修改这些条件,那么

wait()
登录后复制
/
notify()
登录后复制
是唯一的选择。否则,如果只是简单的暂停,
sleep()
登录后复制
就足够了。

Object.wait()方法使用时有哪些常见的陷阱和最佳实践?

Object.wait()
登录后复制
虽然强大,但使用起来却充满了“陷阱”,稍有不慎就可能导致死锁、活锁或程序行为异常。

稿定AI社区
稿定AI社区

在线AI创意灵感社区

稿定AI社区 60
查看详情 稿定AI社区

常见陷阱:

  1. 不在

    synchronized
    登录后复制
    块中调用
    wait()
    登录后复制
    这是最常见的错误。
    wait()
    登录后复制
    notify()
    登录后复制
    notifyAll()
    登录后复制
    必须在同步块内部调用,并且同步的对象必须是调用这些方法的对象。否则,会立即抛出
    IllegalMonitorStateException
    登录后复制

    // 错误示例
    // lock.wait(); // 如果不在synchronized(lock)块中,会抛出IllegalMonitorStateException
    登录后复制
  2. 虚假唤醒(Spurious Wakeups): 线程可能在没有收到

    notify()
    登录后复制
    notifyAll()
    登录后复制
    的情况下被唤醒。这是Java虚拟机规范允许的行为。如果你的代码仅仅使用
    if (condition)
    登录后复制
    来检查条件,那么在虚假唤醒后,线程可能在条件未满足的情况下继续执行,导致逻辑错误。

    // 错误示例 (可能导致虚假唤醒后条件不满足却继续执行)
    // if (!conditionMet) {
    //     lock.wait();
    // }
    登录后复制
  3. notify()
    登录后复制
    notifyAll()
    登录后复制
    的误用:

    • notify()
      登录后复制
      只会唤醒一个等待线程(具体是哪一个由JVM决定,通常是等待时间最长的)。如果多个线程在等待不同的条件,或者你需要所有等待的线程都检查条件,那么使用
      notify()
      登录后复制
      可能会导致某些线程永远得不到唤醒(“信号丢失”)。
    • 在生产者-消费者场景中,如果生产者
      notify()
      登录后复制
      了一个消费者,但队列仍然为空,那么被唤醒的消费者可能再次
      wait()
      登录后复制
      ,而此时可能还有其他消费者在等待,它们就永远不会被唤醒。
  4. 死锁: 如果

    notify()
    登录后复制
    /
    notifyAll()
    登录后复制
    的线程没有正确地改变条件,或者
    wait()
    登录后复制
    的线程在唤醒后没有再次检查条件,就可能导致线程永久等待。

  5. 信号丢失: 如果

    notify()
    登录后复制
    wait()
    登录后复制
    之前执行,那么
    notify()
    登录后复制
    的信号就会丢失,后续调用
    wait()
    登录后复制
    的线程将永远等待。

最佳实践:

  1. 始终在

    while
    登录后复制
    循环中检查等待条件: 这是对抗虚假唤醒和确保条件真正满足的关键。当线程被唤醒时,它应该重新评估条件,如果条件仍然不满足,则再次调用
    wait()
    登录后复制

    synchronized (lock) {
        while (!conditionMet) { // 必须使用while循环
            try {
                lock.wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                // 处理中断逻辑,例如重新设置条件,或者退出循环
                return;
            }
        }
        // 条件满足,继续执行
    }
    登录后复制
  2. 总是使用

    notifyAll()
    登录后复制
    而非
    notify()
    登录后复制
    除非你有一个非常明确且经过验证的理由只唤醒一个线程。
    notifyAll()
    登录后复制
    可以确保所有等待的线程都有机会检查条件,避免“信号丢失”或“活锁”的情况。虽然
    notifyAll()
    登录后复制
    可能唤醒一些不必要的线程,导致它们再次
    wait()
    登录后复制
    ,但这种开销通常比死锁或逻辑错误要小得多。

  3. 确保

    notify()
    登录后复制
    /
    notifyAll()
    登录后复制
    在条件改变后且持有锁时调用:
    改变共享变量的状态和调用
    notify()
    登录后复制
    必须在同一个
    synchronized
    登录后复制
    块中完成,这样可以保证原子性,避免竞争条件和信号丢失。

    synchronized (lock) {
        conditionMet = true; // 改变条件
        lock.notifyAll();    // 唤醒等待线程
    }
    登录后复制
  4. 优先使用

    java.util.concurrent
    登录后复制
    包中的高级并发工具: Java并发包提供了许多更高级、更健壮、更易于使用的工具,如
    BlockingQueue
    登录后复制
    CountDownLatch
    登录后复制
    Semaphore
    登录后复制
    CyclicBarrier
    登录后复制
    ReentrantLock
    登录后复制
    配合
    Condition
    登录后复制
    等。它们在底层封装了
    wait()
    登录后复制
    /
    notify()
    登录后复制
    的复杂性,并处理了许多常见的陷阱,大大降低了并发编程的难度。

    • 例如,对于生产者-消费者模式,直接使用
      ArrayBlockingQueue
      登录后复制
      LinkedBlockingQueue
      登录后复制
      远比手动编写
      wait()
      登录后复制
      /
      notify()
      登录后复制
      安全和高效。
    • 如果需要更细粒度的条件等待,
      ReentrantLock
      登录后复制
      newCondition()
      登录后复制
      方法返回的
      Condition
      登录后复制
      对象提供了
      await()
      登录后复制
      signal()
      登录后复制
      signalAll()
      登录后复制
      ,它们的功能与
      Object.wait()
      登录后复制
      notify()
      登录后复制
      notifyAll()
      登录后复制
      类似,但提供了更强大的灵活性和控制力,并且可以有多个条件变量。

遵循这些最佳实践,可以帮助你编写出更可靠、更易于维护的并发代码。在大多数情况下,如果不是在编写底层的并发库,你都应该考虑优先使用

java.util.concurrent
登录后复制
包提供的工具。

以上就是Java中Thread.sleep与wait区别的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

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

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