杰瑞科技汇

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

核心区别速览表

特性 wait() sleep()
所属类 Object 类的方法 Thread 类的静态方法
锁行为 释放当前对象的锁 不释放任何锁
唤醒方式 被其他线程调用 notify()notifyAll() 唤醒 指定的时间到后自动唤醒
使用场景 线程间通信与协作,常用于生产者-消费者模型 让线程暂停执行一段固定时间,不涉及线程间通信
异常处理 必须在 synchronized 块或方法中调用,否则抛出 IllegalMonitorStateException 可以在任何地方调用
本质 是对象监视器(锁)的一部分,用于线程等待 是线程的休眠,与锁无关

详细解析

wait()

wait() 是 Java 中实现线程间协作的核心机制,当一个线程调用某个对象的 wait() 方法时,它会:

Java中wait和sleep有何本质区别?-图1
(图片来源网络,侵删)
  • 释放锁:该线程会立即释放它对该对象的
  • 进入等待队列:该线程会进入该对象的等待队列,并暂停执行。
  • 等待唤醒:它将一直等待,直到其他线程调用该对象的 notify()notifyAll() 方法来唤醒它。
  • 重新获取锁:当它被唤醒后,它不会立即恢复执行,它会尝试重新获取该对象的锁,只有当成功获取锁后,它才能从 wait() 调用的地方继续执行。

使用场景

wait() 主要用于一个线程需要等待另一个线程完成某个特定操作的场景,最经典的例子就是生产者-消费者模型

  • 消费者线程:当缓冲区为空时,消费者线程调用 buffer.wait(),让自己进入等待状态,并释放缓冲区的锁。
  • 生产者线程:生产者向缓冲区中放入一个产品后,调用 buffer.notify(),唤醒可能在等待的消费者线程。

代码示例

public class WaitExample {
    private static final Object lock = new Object();
    public static void main(String[] args) {
        Thread waitingThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程A:准备进入等待状态...");
                try {
                    // 调用wait()会释放lock的锁
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程A:我被唤醒了,继续执行!");
            }
        });
        waitingThread.start();
        // 主线程等待一下,确保线程A已经进入wait状态
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 主线程作为另一个线程,去唤醒等待的线程
        synchronized (lock) {
            System.out.println("主线程:准备唤醒线程A...");
            // 调用notify()会唤醒一个在lock对象上等待的线程
            lock.notify();
        }
    }
}

执行流程:

  1. 线程A启动,获取 lock 对象的锁。
  2. 线程A打印 "准备进入等待状态...",然后调用 lock.wait()
  3. 调用 wait() 后,线程A释放 lock 的锁,并进入等待状态。
  4. 主线程休眠1秒,确保线程A已经进入等待。
  5. 主线程尝试获取 lock 对象的锁,由于线程A已经释放,所以主线程成功获取。
  6. 主线程打印 "准备唤醒线程A...",然后调用 lock.notify()
  7. notify() 唤醒了在 lock 对象上等待的线程A,但此时线程A还不能立即执行,因为它还没有重新获取到 lock 的锁。
  8. 主线程执行完 synchronized 代码块,释放 lock 的锁。
  9. 线程A成功获取 lock 的锁,从 wait() 方法处返回,继续执行,打印 "我被唤醒了,继续执行!"。
  10. 线程A执行完毕,释放 lock 的锁。

sleep()

sleep() 是一个非常简单的方法,它让当前正在执行的线程暂停执行一段指定的时间。

  • 不释放锁:调用 sleep() 的线程在休眠期间,不会释放任何锁
  • 自动唤醒:休眠时间结束后,线程会自动从阻塞状态变为就绪状态,等待CPU调度。
  • 静态方法:因为是静态方法,所以它只对当前正在执行的线程有效,即 Thread.sleep(1000) 等同于 当前线程.sleep(1000)

使用场景

sleep() 主要用于以下场景:

Java中wait和sleep有何本质区别?-图2
(图片来源网络,侵删)
  • 模拟网络延迟或耗时操作。
  • 在轮询(Polling)中,让CPU短暂休息,避免空转占用过多资源。
  • 让程序暂停一段时间,方便调试。

代码示例

public class SleepExample {
    private static final Object lock = new Object();
    public static void main(String[] args) {
        Thread sleepingThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程B:获取了锁,准备sleep 2秒...");
                try {
                    // 调用sleep()不会释放lock的锁
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程B:sleep结束,释放锁。");
            }
        });
        sleepingThread.start();
        // 主线程尝试获取锁
        try {
            // 等待一下,确保线程B已经获取了锁并进入sleep
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (lock) {
            System.out.println("主线程:尝试获取锁...");
            // 这行代码会在线程B释放锁之前一直等待
            System.out.println("主线程:成功获取了锁!");
        }
    }
}

执行流程:

  1. 线程B启动,获取 lock 对象的锁。
  2. 线程B打印 "获取了锁,准备sleep 2秒...",然后调用 Thread.sleep(2000)
  3. 线程B进入休眠状态,但没有释放 lock 的锁
  4. 主线程休眠500毫秒,然后尝试获取 lock 的锁,由于线程B仍然持有锁,主线程被阻塞。
  5. 2秒后,线程B的休眠结束,它继续执行 synchronized 代码块,打印 "sleep结束,释放锁。",然后释放 lock 的锁。
  6. 主线程此时才能获取到 lock 的锁,打印 "成功获取了锁!"。

总结与类比

为了更好地理解,我们可以用一个生活中的例子来类比:

想象一个公共卫生间(对象锁):

  • wait()

    Java中wait和sleep有何本质区别?-图3
    (图片来源网络,侵删)
    • 你(线程A)走进卫生间,发现里面没纸了。
    • 你不能一直占着卫生间等别人送纸,于是你走出卫生间,并对门口的服务员(对象)说:“卫生间没纸了,请送纸来后叫我一下”(lock.wait())。
    • 你(线程A)释放了对卫生间的占用(释放锁),在休息区等待(进入等待队列)。
    • 服务员(另一个线程)送完纸后,在休息区找到你,说:“纸送到了”(lock.notify())。
    • 你(线程A)得到通知,然后再次去卫生间门口排队,等没人用的时候你再进去(重新获取锁)。
  • sleep()

    • 你(线程B)走进卫生间,准备上大号。
    • 你发现手机快没电了,于是你决定充电5分钟(Thread.sleep(5000))。
    • 在这5分钟里,你虽然坐在马桶上没动,但你仍然占着卫生间不释放锁)。
    • 无论外面的人(主线程)多着急,他们都必须等你5分钟充电结束,你走出卫生间后才能进去。

最佳实践

  1. 永远不要在循环外使用 wait():因为 wait() 可能被意外唤醒(虚假唤醒),所以正确的做法是总是在一个 while 循环中检查条件。

    while (!condition) {
        object.wait();
    }
  2. 优先使用 java.util.concurrent:在现代Java开发中,对于复杂的线程协作,应该优先使用 BlockingQueueCountDownLatchSemaphore 等高级并发工具,它们比直接使用 wait()notify() 更安全、更易用、不易出错。

  3. 明确使用意图:如果你的线程需要等待某个条件成立,请使用 wait(),如果你的线程只是需要暂停一段时间,请使用 sleep(),不要混用。

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