杰瑞科技汇

Java线程中wait与sleep有何核心区别?

核心概念一句话总结

  • sleep() (睡眠):是 Thread 类的一个静态方法,它让当前线程进入“休眠”状态,暂停指定的时间,但不会释放任何锁,它像一个“闹钟”,时间到了线程就会自动苏醒。
  • wait() (等待):是 Object 类的一个方法,它让当前线程进入该对象的“等待池”,会立即释放当前持有的锁,它像一个“人质”,必须由其他线程(通常是生产者)通过 notify()notifyAll() 来“解救”才能苏醒。

详细对比表格

特性 sleep(long millis) wait() / wait(long timeout)
所属类 java.lang.Thread java.lang.Object
作用 让当前线程暂停执行指定毫秒数。 让当前线程等待,直到其他线程调用该对象的 notify()notifyAll()
锁的释放 不释放锁,线程在睡眠期间仍然持有监视器锁。 立即释放锁,这是 wait()sleep() 最核心的区别。
唤醒方式 自动唤醒,时间到了,线程会自动从 Runnable 状态变为 Runnable 状态,等待 CPU 调度。 被动唤醒,必须由其他线程调用 notify() / notifyAll() 来唤醒,也可以设置超时时间,超时后自动唤醒。
使用位置 可以在任何地方调用,因为它是一个静态方法。 只能在同步代码块或同步方法中调用,即,必须在持有锁的情况下才能调用 wait(),否则会抛出 IllegalMonitorStateException
异常处理 只抛出 InterruptedException 抛出 InterruptedExceptionIllegalMonitorStateException
本质 是线程级别的操作,它只是让线程暂停,不关心锁的状态。 是对象级别的操作,它与对象的监视器锁(锁机制)紧密相关。

代码示例演示区别

通过一个经典的“生产者-消费者”模型,我们可以非常清晰地看到 wait()notify() 的作用,以及为什么 sleep() 在这个场景下不适用。

Java线程中wait与sleep有何核心区别?-图1
(图片来源网络,侵删)

场景:一个共享的缓冲区,生产者生产数据,消费者消费数据。

使用 wait()notify() 的正确方式

这是实现线程间协作的标准方式。

共享资源类 (Resource.java)

public class Resource {
    private String name;
    private int count = 1;
    private boolean flag = false; // 标记资源是否可用
    // 生产者
    public synchronized void produce(String name) {
        // 如果资源已经存在,生产者就等待
        while (flag) { // 使用 while 而不是 if,防止虚假唤醒
            try {
                wait(); // 释放锁,并进入等待状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.name = name + "---" + count++;
        System.out.println(Thread.currentThread().getName() + "...生产者... " + this.name);
        flag = true;
        notifyAll(); // 唤醒在等待池中的所有线程(可能是消费者或其他生产者)
    }
    // 消费者
    public synchronized void consume() {
        // 如果资源不存在,消费者就等待
        while (!flag) {
            try {
                wait(); // 释放锁,并进入等待状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "...消费者... " + this.name);
        flag = false;
        notifyAll(); // 唤醒在等待池中的所有线程(可能是生产者或其他消费者)
    }
}

生产者线程 (Producer.java)

public class Producer implements Runnable {
    private Resource resource;
    public Producer(Resource resource) {
        this.resource = resource;
    }
    @Override
    public void run() {
        while (true) {
            resource.produce("商品");
        }
    }
}

消费者线程 (Consumer.java)

Java线程中wait与sleep有何核心区别?-图2
(图片来源网络,侵删)
public class Consumer implements Runnable {
    private Resource resource;
    public Consumer(Resource resource) {
        this.resource = resource;
    }
    @Override
    public void run() {
        while (true) {
            resource.consume();
        }
    }
}

测试类 (WaitSleepDemo.java)

public class WaitSleepDemo {
    public static void main(String[] args) {
        Resource resource = new Resource();
        new Thread(new Producer(resource), "生产者A").start();
        new Thread(new Consumer(resource), "消费者B").start();
    }
}

执行流程分析:

  1. 假设 生产者A 先运行,它调用 resource.produce(),因为 flagfalse,所以它不会进入 while 循环,直接生产数据,并将 flag 设为 true,然后调用 notifyAll()
  2. 消费者B 运行,调用 resource.consume(),因为 flagtrue,所以它消费数据,并将 flag 设为 false,然后调用 notifyAll()
  3. 生产者A 再次尝试运行时,flagfalse,它再次生产... 如此循环。
  4. 关键点:当 生产者A 调用 wait() 时,它会释放 resource 对象的锁,这时 消费者B 就能成功获取到锁,进入 consume() 方法,反之亦然。它们通过锁和 wait/notify 实现了完美的协作。

为什么不能用 sleep() 替代 wait()

如果我们错误地在 Resource 类中使用 sleep(),会发生什么?

错误的代码示例 (仅修改 produceconsume 方法)

Java线程中wait与sleep有何核心区别?-图3
(图片来源网络,侵删)
// 错误的 produce
public synchronized void produce(String name) {
    if (flag) { // 使用 if 而不是 while
        try {
            System.out.println("生产者发现资源已满,准备睡觉...");
            Thread.sleep(100); // 错误!使用 sleep
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    // ... 生产逻辑 ...
    flag = true;
    notifyAll();
}
// 错误的 consume
public synchronized void consume() {
    if (!flag) {
        try {
            System.out.println("消费者发现资源为空,准备睡觉...");
            Thread.sleep(100); // 错误!使用 sleep
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    // ... 消费逻辑 ...
    flag = false;
    notifyAll();
}

执行流程分析:

  1. 生产者A 运行,生产一个商品,flag 变为 true
  2. 生产者A 再次运行,发现 flagtrue,于是它调用 Thread.sleep(100)注意:它并没有释放锁!
  3. 消费者B 想要运行,但它无法获取 resource 对象的锁,因为 生产者A 还在同步代码块里睡觉!
  4. 结果就是:生产者A 睡着了,并且一直霸占着锁,导致消费者B 永远无法运行,程序卡死。

这个例子完美地证明了:sleep() 不释放锁,因此在需要让出资源给其他线程的协作场景下,它完全无效。


总结与最佳实践

  1. 功能定位

    • sleep():用于控制执行节奏,当你想让一个线程暂停一段固定时间,但不希望它影响其他线程时使用,一个轮询任务,每秒检查一次状态。
    • wait() / notify():用于线程间通信与协作,当一个线程需要等待某个条件满足时(如资源可用、数据就绪),使用 wait() 让出锁并等待;当条件满足时,其他线程使用 notify() 来唤醒它。
  2. 记住核心区别

    • sleep() 是 Thread 的,wait() 是 Object 的。
    • sleep() 不释放锁,wait() 释放锁。
    • sleep() 自动醒,wait() 被动醒。
  3. 现代替代方案: 在 Java 5 之后,java.util.concurrent.locks 包提供了更强大、更灵活的锁机制(如 ReentrantLock),以及与 Condition 对象配合的 await()signal() 方法,它们在功能上与 wait()notify() 类似,但提供了更高级的功能,比如可以创建多个 Condition(相当于多个等待队列),并且避免了 notifyAll() 带来的“伪唤醒”问题,在复杂的并发场景下,推荐使用 ReentrantLockCondition

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

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