基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

sleep和wait区别?

知识点图片

本文对比sleepwait。核心区别:sleep是线程暂停,不释放锁;wait是线程等待,会释放锁,用于线程间通信。

这是一个非常经典且重要的 Java 多线程面试题。sleep()wait() 的区别是理解 Java 线程同步和通信的关键。

我会从多个维度来详细解释它们的区别,并提供一个生动的比喻和代码示例来帮助你彻底理解。


一、核心区别(一句话总结)

  • sleep() 是让线程“睡一会”,时间到了自己会醒,但不会释放它持有的锁
  • wait() 是让线程进入“等待状态”,它会释放持有的锁,并且需要其他线程通过 notify()notifyAll() 来唤醒。

二、详细对比表格

特性 Thread.sleep() Object.wait()
归属类 java.lang.Thread java.lang.Object
锁的释放 不释放锁 释放锁 (这是最本质的区别)
使用前提 可以在任何地方使用 必须在同步代码块或同步方法中 (synchronized) 使用
唤醒条件 1. 睡眠时间到
2. 被 interrupt() 中断
1. 其他线程调用该对象的 notify()notifyAll()
2. wait(long timeout) 时间到
3. 被 interrupt() 中断
方法类型 static 静态方法,控制的是当前线程 final 实例方法,控制的是调用该方法的对象上的等待线程
主要用途 暂停线程,让出 CPU 时间片,但不涉及线程间协作 线程间的通信与协作

三、深入解析

1. 归属类不同

  • sleep(long millis)Thread 类的静态方法。这意味着它直接作用于当前正在执行的线程。你调用 Thread.sleep(1000),就是让当前线程休眠1秒。即使你用一个线程实例去调用 t1.sleep(1000),休眠的仍然是当前执行这段代码的线程,而不是 t1 线程。
  • wait()Object 类的方法。这意味着任何 Java 对象都可以调用它。它作用于调用 wait() 方法的那个对象,让当前线程在该对象上等待。

2. 对锁的处理(最重要的区别)

这是面试官最想听到的点。

  • sleep() 不会释放锁:如果一个线程在 synchronized 代码块中调用了 sleep(),它会暂停执行,但它仍然持有这个锁。其他线程无法进入这个 synchronized 代码块,必须等待 sleep() 的线程醒来并执行完毕后释放锁。
  • wait() 会释放锁:如果一个线程在 synchronized 代码块中调用了某个对象的 wait() 方法,它会立即释放该对象的锁,并进入该对象的等待队列(Waiting Queue)。这样,其他线程就有机会获得这个锁并进入 synchronized 代码块。

3. 使用前提不同

  • sleep() 可以在任何地方调用。
  • wait() 必须synchronized 方法或 synchronized 代码块中调用。否则,程序会在运行时抛出 IllegalMonitorStateException 异常。这是因为 wait() 的操作是基于锁的,你必须先持有这个锁,才能在它上面等待和释放。

4. 唤醒方式不同

  • sleep() 的线程会在指定的时间后自动唤醒,或者被其他线程调用 interrupt() 方法中断时唤醒。
  • wait() 的线程需要依赖其他线程。当另一个线程获得了同一个对象的锁后,调用该对象的 notify()(随机唤醒一个等待的线程)或 notifyAll()(唤醒所有等待的- 线程)时,等待的线程才会被唤醒,并重新尝试获取锁。

四、场景比喻

想象一个公共卫生间,只有一个坑位,门上有一把锁(Lock)。

  • sleep() 的场景:

    • 张三进了卫生间,把门锁上了 (synchronized)。
    • 他突然觉得有点困,就坐在马桶上睡了10分钟 (sleep(600000))。
    • 在这10分钟里,他一直占着坑位,并且门是锁着的(不释放锁)。
    • 外面的李四内急,想进去,但门被锁着,只能在外面干等着,直到张三睡醒、办完事、出来、把门打开(释放锁)。
    • 总结sleep 是自己暂停,但不影响自己对资源的占用。
  • wait() / notify() 的场景:

    • 张三进了卫生间,把门锁上了 (synchronized)。
    • 他发现没纸了,他不能继续了。于是他打开门,对外面喊:“谁给我送点纸过来?” (wait())。喊完后,他就走出了坑位,把门也解锁了(释放锁),然后在门口等着。
    • 这时,李四可以进去了。他进去后,把门锁上 (synchronized)。
    • 李四办完事,看到旁边有一卷新纸,他把纸放在了纸架上,然后冲着门外的张三喊:“纸来了!” (notify())。
    • 李四办完事出来,解锁开门(释放锁)。
    • 张三听到后,重新进去,锁上门(再次获取锁),拿到纸,继续他的事情。
    • 总结wait 是因为条件不满足而主动让出资源,等待别人满足条件后通知自己。这是典型的线程间协作

五、代码示例

这个例子清晰地展示了 sleepwait 对锁的不同处理。

java
public class SleepVsWait {

    private static final Object LOCK = new Object();

    public static void main(String[] args) {
        // 演示 sleep
        new Thread(() -> {
            System.out.println("Thread A 准备获取锁...");
            synchronized (LOCK) {
                System.out.println("Thread A 获取到锁");
                try {
                    System.out.println("Thread A sleep 2秒, 但不释放锁");
                    Thread.sleep(2000); // 持有锁,睡2秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread A 睡醒了,执行完毕,释放锁");
            }
        }, "Thread A").start();

        // 给A一点时间先启动
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 演示 wait
        new Thread(() -> {
            System.out.println("Thread B 准备获取锁...");
            synchronized (LOCK) {
                System.out.println("Thread B 获取到锁");
                try {
                    System.out.println("Thread B wait, 释放锁,等待被唤醒");
                    LOCK.wait(); // 释放锁,并在此等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread B 被唤醒了,重新获取锁并执行完毕");
            }
        }, "Thread B").start();
        
        // 演示 notify
        new Thread(() -> {
            System.out.println("Thread C 准备获取锁...");
            synchronized (LOCK) {
                System.out.println("Thread C 获取到锁");
                System.out.println("Thread C 调用 notify() 唤醒一个等待的线程");
                LOCK.notify(); // 唤醒在LOCK上等待的线程B
                System.out.println("Thread C 执行完毕,释放锁");
                // 注意:notify()后不会立即释放锁,要等同步代码块执行完
            }
        }, "Thread C").start();
    }
}

代码执行顺序分析 (一个可能的输出):

  1. Thread A 启动,获取 LOCK 锁,打印信息,然后 sleep(2000)。此时它持有锁
  2. Thread B 启动,尝试获取 LOCK 锁,但因为 A 持有锁,B 被阻塞,无法进入同步块。
  3. Thread C 启动,尝试获取 LOCK 锁,同样被阻塞。
  4. 2秒后,Thread A 睡醒,执行完同步块,释放锁
  5. Thread BThread C 竞争锁。假设 Thread B 抢到了。
  6. Thread B 进入同步块,打印信息,然后调用 LOCK.wait()。它释放锁并进入等待状态。
  7. Thread C 现在可以获取 LOCK 锁了,因为它被 B 释放了。
  8. Thread C 进入同步块,打印信息,调用 LOCK.notify()。这个通知会发给正在等待的 B
  9. Thread C 执行完同步块,释放锁
  10. Thread B 被唤醒,它会重新尝试获取锁。一旦获取成功,它会从 wait() 的地方继续执行,打印信息,然后执行完毕。

这个例子完美地展示了 sleep 占着锁睡觉,而 wait 释放锁让别人先干的本质区别。

00:00
00:00