杰瑞科技汇

Java中sleep和wait到底有何本质区别?

核心区别速览表

特性 sleep() wait()
所属类 java.lang.Thread java.lang.Object
锁的释放 不释放 释放
唤醒方式 自动唤醒(时间到) 被动唤醒(需要 notify()notifyAll()
使用场景 暂停当前线程,不涉及线程间通信 线程间通信,让线程等待某个条件满足
调用前提 可以在任何地方调用 必须在同步代码块或同步方法中调用
异常处理 不抛出 InterruptedException(但方法会检查中断状态并返回) 抛出 InterruptedException
参数 指定毫秒数或纳秒数 可以不带参数(永久等待)或带超时时间

详细解释与代码示例

sleep() - 线程的“小憩”

sleep()Thread 类的一个静态方法,它就像让你“小憩”一会儿,设定一个闹钟,时间到了自然就醒。

Java中sleep和wait到底有何本质区别?-图1
(图片来源网络,侵删)

核心特点:

  • 不释放锁:当一个线程调用 sleep() 进入休眠时,它仍然持有它所获得的锁,这意味着其他需要这个锁的线程必须继续等待,即使调用 sleep() 的线程已经“睡着了”。
  • 自动唤醒sleep() 方法会指定一个休眠时间,当时间到达后,JVM 会将线程从“阻塞”(Blocked)状态变回“就绪”(Runnable)状态,等待 CPU 调度。

代码示例:

public class SleepExample {
    public static void main(String[] args) {
        Runnable task = () -> {
            synchronized (SleepExample.class) { // 获取锁
                System.out.println(Thread.currentThread().getName() + " 获取到锁,准备睡觉...");
                try {
                    Thread.sleep(3000); // 睡 3 秒,期间不释放锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 醒来了,继续执行。");
            }
        };
        new Thread(task, "线程A").start();
        new Thread(task, "线程B").start();
    }
}

执行结果分析:

  1. 线程A 和 线程B 都会竞争 SleepExample.class 这个锁。
  2. 假设线程A 先获取到锁,它会打印“准备睡觉...”,然后调用 sleep(3000)
  3. 关键点:尽管线程A 睡着了,但它仍然持有锁,线程B 无法进入 synchronized 代码块,只能在外面等待。
  4. 3秒后,线程A 醒来,打印“醒来了...”,然后释放锁,此时线程B 才能获取锁并执行。

wait() - 线程的“耐心等待”

wait()Object 类的一个实例方法,它不像 sleep() 那样设定闹钟,而是让你“耐心等待”,直到有人告诉你“可以继续了”(通过 notify()notifyAll())。

Java中sleep和wait到底有何本质区别?-图2
(图片来源网络,侵删)

核心特点:

  • 释放锁:当一个线程调用 wait() 时,它会立即释放当前对象锁,并进入该对象的“等待队列”(Wait Set)中,这是它与 sleep() 最根本的区别。
  • 被动唤醒:线程不会自己醒来,它必须由另一个持有同一个锁的线程调用 notify()(随机唤醒一个等待的线程)或 notifyAll()(唤醒所有等待的线程)来唤醒。
  • 必须在同步块/方法中调用:因为 wait() 会释放锁,所以它必须在持有锁的情况下才能被调用,否则会抛出 IllegalMonitorStateException 异常。

代码示例:

public class WaitExample {
    private static final Object lock = new Object();
    public static void main(String[] args) {
        Runnable task = () -> {
            synchronized (lock) { // 必须在同步块中调用 wait()
                System.out.println(Thread.currentThread().getName() + " 获取到锁,但需要等待条件...");
                try {
                    lock.wait(); // 释放锁,进入等待状态
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 收到通知,继续执行。");
            }
        };
        new Thread(task, "等待线程A").start();
        try {
            Thread.sleep(1000); // 主线程睡1秒,确保等待线程A已经进入wait状态
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 另一个线程来唤醒它
        Runnable notifierTask = () -> {
            synchronized (lock) {
                System.out.println("通知线程 获取到锁,准备唤醒等待的线程。");
                lock.notify(); // 唤醒一个在lock对象上等待的线程
                System.out.println("通知线程 已发出通知,但还未释放锁。");
            }
            // 同步块执行完毕,通知线程释放锁
            System.out.println("通知线程 释放了锁。");
        };
        new Thread(notifierTask, "通知线程").start();
    }
}

执行结果分析:

  1. 等待线程A 获取 lock 对象的锁,打印“需要等待条件...”,然后调用 lock.wait()
  2. 关键点:等待线程A 立即释放了 lock,并进入等待状态。
  3. 通知线程可以轻松地获取到 lock 锁。
  4. 通知线程调用 lock.notify(),这会唤醒等待线程A。
  5. 重要:被唤醒的线程(等待线程A)不会立即执行,它需要重新获取到 lock,由于此时通知线程还持有锁,所以等待线程A会进入“锁池”(Blocked on lock for lock)中等待。
  6. 当通知线程执行完 synchronized 代码块并释放锁后,等待线程A才能获取锁,从 wait() 的下一句代码开始执行,打印“收到通知...”。

生动的比喻:咖啡厅排队

想象一下你在一个咖啡厅排队买咖啡。

Java中sleep和wait到底有何本质区别?-图3
(图片来源网络,侵删)
  • sleep() 就像是你排队时去趟洗手间。

    • 你暂时离开队伍(暂停执行)。
    • 你并没有把你的位置让给别人(不释放锁),队伍里的人都知道你只是去一下,很快就会回来,所以没人会插你的队。
    • 你设定了5分钟的时间(sleep(5000)),5分钟后你自然回到队伍里(自动唤醒)。
  • wait() 就像是你发现咖啡机坏了,决定去旁边的座位坐着等。

    • 你明确告诉排在你后面的人:“我不等了,你们先买吧”(释放锁)。
    • 你走到一个专门的“等待区”(等待队列)坐下。
    • 你不会自己站起来走回队伍,而是耐心等待咖啡店员(另一个线程)喊你的名字(notify())或者喊所有等的人(notifyAll())。
    • 当你被叫到名字时,你需要重新回到队伍的末尾(重新竞争锁),等待轮到你才能买咖啡(重新获取锁后继续执行)。

总结与何时使用

使用场景 推荐方法 原因
我只需要让当前线程暂停一段时间,不需要和其他线程交互。 Thread.sleep() 简单直接,不涉及复杂的同步和唤醒机制。
我需要让一个线程等待,直到另一个线程完成某个操作或某个条件变为真。 Object.wait() / notify() 这是 Java 中实现线程间通信和协作的标准机制,能高效地管理线程的生命周期。

记住黄金法则:

  • sleep()Thread 的方法,用于控制自身不释放锁
  • wait()Object 的方法,用于线程间通信必须释放锁
分享:
扫描分享到社交APP
上一篇
下一篇