杰瑞科技汇

java sleep wait 区别

这是一个非常经典且重要的面试题,理解它们的差异是掌握 Java 多线程编程的关键。

java sleep wait 区别-图1
(图片来源网络,侵删)

一句话核心区别

sleep()线程 的方法,它让当前线程暂停执行,但不释放锁wait()对象 的方法,它让当前线程等待,并立即释放锁

为了更清晰地理解,我们从多个维度进行详细对比。


详细对比表格

特性 sleep() wait()
所属类 java.lang.Thread java.lang.Object
调用者 在当前线程上直接调用 同步代码块同步方法中,通过锁对象(this 或其他对象)调用
锁的释放 不释放任何锁,线程在睡眠期间,仍然持有它获取的所有锁。 立即释放锁,调用 wait() 的线程会释放其持有的锁,以便其他线程可以获取该锁。
唤醒方式 指定时间到期后自动唤醒
被其他线程中断 (interrupt())。
其他线程调用 notify()notifyAll()
指定等待时间到期(如果调用了 wait(long timeout))。
被其他线程中断
使用场景 主要用于暂停当前线程的执行,通常用于简单的定时、延时或轮询场景。 主要用于线程间的通信与协作,当一个线程需要等待某个条件(如资源可用)时,它调用 wait() 进入等待状态,直到其他线程满足条件后调用 notify() 唤醒它。
是否需要同步 不需要,可以在任何地方调用。 必须同步上下文synchronized 代码块或方法)中调用,否则会抛出 IllegalMonitorStateException
异常处理 抛出 InterruptedException 抛出 InterruptedException

深入解析与代码示例

sleep() - 线程的“暂停键”

sleep() 就像一个线程的“暂停键”,它告诉线程:“嘿,你先别运行了,睡一会儿,时间到了我再继续。”

特点:

java sleep wait 区别-图2
(图片来源网络,侵删)
  • 无锁关系sleep() 和锁没有任何关系,一个线程在 sleep 时,它依然牢牢地抓着它获取的锁,其他想访问这个同步资源的线程只能干等着。

示例代码:

class SleepDemo {
    private static final Object lock = new Object();
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程 A: 获取到锁,准备执行 3 秒...");
                try {
                    // 线程 A 睡觉,但依然持有锁
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程 A: 醒来了,继续执行。");
            }
        }, "线程 A").start();
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程 B: 必须等待线程 A 释放锁才能进入。");
            }
        }, "线程 B").start();
    }
}

输出结果分析: 你会看到 "线程 A: 获取到锁..." 打印后,立即会等待 3 秒,然后才会打印 "线程 A: 醒来了...",在这 3 秒内,"线程 B" 的打印语句绝对不会出现,因为即使线程 A 在 sleep,它也持有 lock 对象的锁,线程 B 无法进入 synchronized 代码块。


wait() - 线程的“等待协作”

wait() 是多线程协作的基石,它就像一个线程说:“我现在需要的条件不满足,我先不干了,我把锁让出来,谁满足了条件记得叫我一声(notify)。”

特点:

java sleep wait 区别-图3
(图片来源网络,侵删)
  • 与锁强绑定wait() 必须在 synchronized 代码块中调用,因为它操作的是“锁”的状态,调用 wait() 的线程会释放锁,并进入该对象的等待队列。
  • 需要被唤醒wait() 不会自己醒来(除非设置了超时),必须由其他线程调用同一个对象的 notify()notifyAll() 来唤醒。

示例代码:

class WaitNotifyDemo {
    private static final Object lock = new Object();
    private static boolean condition = false; // 共享条件
    public static void main(String[] args) {
        // 等待线程
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("等待线程: 检查条件,条件不满足,开始等待...");
                while (!condition) { // 使用 while 循环防止虚假唤醒
                    try {
                        // 释放锁,进入等待状态
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("等待线程: 条件已满足,被唤醒,继续执行!");
            }
        }, "等待线程").start();
        // 通知线程
        try {
            Thread.sleep(1000); // 让等待线程先执行并进入等待状态
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("通知线程: 准备满足条件...");
                condition = true; // 修改共享状态
                System.out.println("通知线程: 条件已满足,唤醒等待线程。");
                // 唤醒在 lock 对象上等待的一个线程
                lock.notify();
            }
        }, "通知线程").start();
    }
}

输出结果分析:

  1. "等待线程" 启动,获取锁,发现 conditionfalse,调用 lock.wait()
  2. 调用 wait() 后,"等待线程"立即释放锁,并进入 lock 对象的等待队列,阻塞。
  3. "通知线程" 启动,由于 "等待线程" 已经释放了锁,"通知线程" 可以顺利获取 lock
  4. "通知线程" 修改 condition = true,然后调用 lock.notify()
  5. notify() 会从 lock 的等待队列中随机挑选一个线程(这里是"等待线程")唤醒,并将其移入锁的同步队列
  6. "通知线程" 释放锁后,"等待线程" 从同步队列中获取锁,从 wait() 的地方恢复执行
  7. "等待线程" 再次检查 while (!condition) 循环,conditiontrue,循环退出,继续执行后续代码。

总结与最佳实践

  1. 功能不同

    • sleep() 用于控制时间,让线程暂时休眠。
    • wait() 用于控制流程,实现线程间的协作通信。
  2. 锁处理是核心

    • sleep() 不释放锁wait() 释放锁,这是两者最根本、最重要的区别。
  3. 使用 wait() 时的最佳实践

    • 永远在 while 循环中检查条件while (!condition) { lock.wait(); },因为 wait() 可能被意外地“虚假唤醒”(Spurious Wakeup),即在没有被 notify() 的情况下线程也可能苏醒,使用 while 循环可以确保在苏醒后再次检查条件是否真的满足。
    • 必须在 synchronized 代码块中调用:这是为了保证线程对共享状态的互斥访问。
  4. 如何选择?

    • 如果你只是想让当前线程停顿一段时间(比如定时任务、延时),使用 Thread.sleep()
    • 如果你需要根据某个共享状态来协调多个线程的执行顺序(比如生产者-消费者模型),使用 Object.wait()Object.notify()/notifyAll()

一个生动的比喻

想象一个办公室(共享资源)里有一间会议室()。

  • sleep():小王(线程 A)在开会,他突然有点困,于是说:“我先睡 10 分钟,别打扰我。” 他关着门睡觉,会议室还是被他占着,小李(线程 B)想来开会,只能在外面等着。

  • wait():小王在开会,但他发现会议资料(条件)还没到,他无法继续,于是他对同事说:“我先出去等一下,资料到了麻烦叫我一声。” 说完,他打开门离开了会议室释放锁),去茶水间(等待队列)等待,这时,小李就可以进入会议室开会了,过了一会儿,资料送到了,同事(通知线程)去茶水间找到小王,告诉他:“资料到了!”(notify()),小王回到会议室门口排队(同步队列),等小李开完会出来(释放锁),小王再进去继续开会。

希望这个详细的解释和比喻能帮助你彻底理解 sleep()wait() 的区别!

分享:
扫描分享到社交APP
上一篇
下一篇