杰瑞科技汇

Java wait 与 sleep 底层实现与应用场景有何不同?

这是一个非常经典且重要的面试题,理解它们的区别对于掌握 Java 并发编程至关重要。

Java wait 与 sleep 底层实现与应用场景有何不同?-图1
(图片来源网络,侵删)

一句话概括核心区别:

  • sleep() 是让线程暂停执行,它不释放任何锁,它像一个“霸道”的同事,说“我休息10分钟,别叫我,也别想动我的东西”。
  • wait() 是让线程等待,它会释放对象锁,它像一个“有礼貌”的同事,说“我需要这个东西,你先用吧,用完了告诉我一声(notify()),我再用”。

下面我们从多个维度进行详细对比。


核心区别对比表

特性 sleep() wait()
所属类 java.lang.Thread 的静态方法 java.lang.Object 的实例方法
锁的释放 不释放任何锁 释放当前对象的锁
使用位置 可以在任何地方使用 只能在同步代码块或同步方法中使用
唤醒方式 自动唤醒,到达指定时间后 必须被其他线程唤醒notify()notifyAll()
异常处理 不抛出 InterruptedException 抛出 InterruptedException
用途 一般用于暂停执行,不涉及线程间通信 主要用于线程间通信和同步
调用者 任意线程 获取了对象锁的线程

详细解释

sleep() 方法

sleep()Thread 类的一个静态方法。

语法:

Java wait 与 sleep 底层实现与应用场景有何不同?-图2
(图片来源网络,侵删)
public static native void sleep(long millis) throws InterruptedException;

工作原理:

  • 当一个线程调用 sleep() 时,它会让出 CPU 的使用权,进入阻塞状态。
  • 在指定的 millis 毫秒时间过后,该线程会自动从阻塞状态转变为就绪状态,等待 CPU 再次调度执行。
  • 最关键的一点:在整个 sleep 期间,该线程不会释放它所持有的任何锁

使用场景:

  • 通常用于简单的、不需要线程间交互的延迟,模拟一个耗时操作,或者实现一个简单的轮询机制。

示例代码:

public class SleepExample {
    public static void main(String[] args) {
        Runnable task = () -> {
            synchronized (SleepExample.class) { // 获取锁
                System.out.println("线程 " + Thread.currentThread().getName() + " 获取了锁,准备进入 sleep");
                try {
                    // 睡眠2秒,不释放锁
                    Thread.sleep(2000); 
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程 " + Thread.currentThread().getName() + " 从 sleep 中醒来,继续执行");
            }
        };
        Thread t1 = new Thread(task, "Thread-1");
        Thread t2 = new Thread(task, "Thread-2");
        t1.start();
        t2.start();
    }
}

运行结果分析: 你会看到 Thread-1 先打印信息,Thread-2 不会立即执行,而是要等到 Thread-1sleep(2000) 结束后,Thread-1 打印完唤醒信息并释放锁,Thread-2 才能获取锁并执行,这证明了 sleep() 没有释放锁。

Java wait 与 sleep 底层实现与应用场景有何不同?-图3
(图片来源网络,侵删)

wait() 方法

wait()Object 类的一个实例方法,这意味着任何 Java 对象都可以调用 wait() 方法

语法:

public final void wait() throws InterruptedException;
public final native void wait(long timeout) throws InterruptedException;

工作原理:

  • wait() 必须在同步代码块(synchronized中调用,因为一个线程只有在持有某个对象的锁时,才能让其他线程等待该对象的状态变化。
  • 当一个线程调用 obj.wait() 时,它会立即释放 obj 对象的锁,然后进入该对象的等待队列,进入阻塞状态。
  • 不会自动醒来,必须由另一个线程调用 obj.notify()obj.notifyAll() 来唤醒它。
  • 当被唤醒后,它会尝试重新获取 obj 对象的锁,一旦获取到锁,它就会从 wait() 调用的地方继续执行。

使用场景:

  • wait()/notify() 是 Java 中实现生产者-消费者任务队列等经典并发模式的基石,它用于一个线程通知另一个线程某个条件已经满足。

示例代码:

public class WaitNotifyExample {
    public static void main(String[] args) {
        Object lock = new Object(); // 共享锁对象
        // 消费者线程
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("消费者:等待商品...");
                try {
                    lock.wait(); // 释放锁,进入等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者:被唤醒,开始消费商品!");
            }
        }, "Consumer").start();
        // 生产者线程
        try {
            Thread.sleep(1000); // 模拟生产耗时
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("生产者:生产好了商品,通知消费者!");
                lock.notify(); // 唤醒在lock上等待的线程
            }
        }, "Producer").start();
    }
}

运行结果分析:

  1. Consumer 线程启动,获取 lock,打印信息,然后调用 lock.wait(),释放 lock 并进入等待状态。
  2. Producer 线程启动,等待1秒后,获取 lock,打印信息,然后调用 lock.notify()
  3. notify() 唤醒 Consumer 线程。Producer 线程继续执行完同步代码块后释放 lock
  4. Consumer 线程从等待状态被唤醒,重新获取 lock,然后从 wait() 方法之后继续执行,打印消费信息。

总结与最佳实践

特性 sleep() wait()
一句话总结 让线程“休息”一会儿,但抱着锁不放 让线程“等待”,并放下锁,等别人叫醒
核心用途 延迟、暂停 线程间通信与同步
关键约束 必须在 synchronized 代码块中

如何选择?

  • 如果你只是想让当前线程暂停一段时间,不关心其他线程的状态,也不需要与它们交互,请使用 Thread.sleep(),每隔一秒检查一次某个标志位。
  • 如果你需要根据某个共享资源的状态来协调多个线程的执行顺序,即一个线程需要等待另一个线程完成某个操作后再继续,请使用 Object.wait()Object.notify()

现代并发工具

值得注意的是,wait()/notify() 机制相对底层且容易出错(忘记在 while 循环中检查条件,导致虚假唤醒问题),在现代 Java 开发中,我们更推荐使用更高级、更安全的并发工具,它们在内部已经帮我们处理了这些复杂性:

  • java.util.concurrent.locks.Condition: 可以看作是 wait()/notify() 的高级版本,一个锁可以绑定多个 Condition,功能更强大。
  • java.util.concurrent.BlockingQueue: 实现了生产者-消费者模式的队列,如 ArrayBlockingQueueLinkedBlockingQueue,它们内部已经实现了线程安全的等待和通知机制,是实际开发中的首选。
  • java.util.concurrent.CountDownLatch / CyclicBarrier / Semaphore: 这些工具类提供了更丰富的同步控制能力。

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

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