杰瑞科技汇

wait和sleep区别,为何同属阻塞却机制迥异?

核心区别一句话总结

sleep() 是让线程暂停执行,它不释放任何锁;而 wait() 是让线程等待,它会释放当前持有的锁

wait和sleep区别,为何同属阻塞却机制迥异?-图1
(图片来源网络,侵删)

详细对比表格

为了更直观地理解,我们通过一个表格来对比它们的主要区别:

特性 sleep() (来自 Thread 类) wait() (来自 Object 类)
所属类 java.lang.Thread java.lang.Object
锁的释放 不释放任何锁。 释放当前对象上的锁。
使用前提 可以在任何地方直接调用。 必须在同步代码块或同步方法中调用,否则会抛出 IllegalMonitorStateException
唤醒方式 指定的时间自动苏醒。
被中断(interrupt())会抛出 InterruptedException
被另一个线程调用 notify()notifyAll() 唤醒。
被中断(interrupt())会抛出 InterruptedException
指定超时时间(wait(long timeout))后自动苏醒。
目的 通常用于暂停一个线程一段时间,不涉及线程间的通信。 通常用于线程间通信,一个线程等待某个条件成立,其他线程改变条件后唤醒它。
异常 调用时被中断会抛出 InterruptedException 调用时被中断会抛出 InterruptedException;在非同步上下文中调用会抛出 IllegalMonitorStateException

代码示例:直观感受区别

通过一个简单的生产者-消费者模型,我们可以非常清楚地看到 wait()sleep() 在行为上的巨大差异。

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

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

这是经典的线程协作模式,当缓冲区满时,生产者等待;当缓冲区空时,消费者等待。

class SharedBuffer {
    private int buffer = 0; // 缓冲区容量为1
    // 生产者方法
    public synchronized void produce() throws InterruptedException {
        // 如果缓冲区有东西,就等待消费者消费
        while (buffer > 0) {
            System.out.println("生产者:缓冲区已满,等待...");
            wait(); // 释放锁,并等待
        }
        // 被唤醒后,执行生产逻辑
        buffer++;
        System.out.println("生产者:生产了1个产品,缓冲区数量: " + buffer);
        // 通知消费者可以消费了
        notify(); // 唤醒在同一个对象上等待的线程
    }
    // 消费者方法
    public synchronized void consume() throws InterruptedException {
        // 如果缓冲区为空,就等待生产者生产
        while (buffer == 0) {
            System.out.println("消费者:缓冲区为空,等待...");
            wait(); // 释放锁,并等待
        }
        // 被唤醒后,执行消费逻辑
        buffer--;
        System.out.println("消费者:消费了1个产品,缓冲区数量: " + buffer);
        // 通知生产者可以生产了
        notify(); // 唤醒在同一个对象上等待的线程
    }
}
public class WaitNotifyExample {
    public static void main(String[] args) {
        SharedBuffer buffer = new SharedBuffer();
        // 生产者线程
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    buffer.produce();
                    Thread.sleep(500); // 模拟生产耗时
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        // 消费者线程
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    buffer.consume();
                    Thread.sleep(1000); // 模拟消费耗时
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

输出分析: 你会发现生产者和消费者会交替执行,当生产者发现缓冲区满时,它会调用 wait()释放锁,这样消费者线程就能获得锁,进入 consume() 方法消费产品,消费完后,消费者调用 notify() 唤醒生产者,这是一个完美的协作过程。

wait和sleep区别,为何同属阻塞却机制迥异?-图2
(图片来源网络,侵删)

错误地使用 sleep() (死锁的例子)

现在我们尝试用 sleep() 来实现同样的逻辑,看看会发生什么。

class BadSharedBuffer {
    private int buffer = 0;
    public synchronized void produce() throws InterruptedException {
        // 错误:使用 sleep 代替 wait
        if (buffer > 0) {
            System.out.println("生产者:缓冲区已满,等待...");
            Thread.sleep(1000); // 线程休眠,但**不释放锁**
        }
        buffer++;
        System.out.println("生产者:生产了1个产品,缓冲区数量: " + buffer);
        // 没有notify,因为sleep不涉及线程唤醒
    }
    public synchronized void consume() throws InterruptedException {
        // 错误:使用 sleep 代替 wait
        if (buffer == 0) {
            System.out.println("消费者:缓冲区为空,等待...");
            Thread.sleep(1000); // 线程休眠,但**不释放锁**
        }
        buffer--;
        System.out.println("消费者:消费了1个产品,缓冲区数量: " + buffer);
    }
}
public class SleepExample {
    public static void main(String[] args) {
        BadSharedBuffer buffer = new BadSharedBuffer();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    buffer.produce();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    buffer.consume();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

输出分析与问题:

  1. 假设生产者先运行,发现 buffer 为 0,成功生产,buffer 变为 1。
  2. 生产者再次运行,发现 buffer 为 1(大于 0),于是进入 if 语句,调用 Thread.sleep(1000)
  3. 关键点:生产者虽然休眠了,但它仍然持有 BadSharedBuffer 对象的锁。
  4. 消费者线程尝试获取锁来执行 consume(),但它被阻塞了,因为它拿不到锁。
  5. 1秒后,生产者苏醒,但因为它仍然持有锁,它会继续下一次循环,再次发现 buffer 为 1,再次进入 sleep,消费者线程永远拿不到锁。

结果:死锁! 这就是 sleep() 不释放锁可能导致的严重问题。


总结与最佳实践

sleep() wait()
使用场景 时间控制:当你想让一个线程暂停一段固定的时间,不关心其他线程的状态时,每隔1秒执行一次任务。 线程协作:当一个线程需要等待另一个线程完成某个操作或某个条件满足时,生产者-消费者、等待队列。
记住的口诀 sleep 是“睡一会儿”,自己醒了,不麻烦别人,也不放手。 wait 是“等一下”,告诉别人“等我”,并把手里的“锁”放下,等别人“通知” (notify) 后再继续。

  • 如果你只是想让程序“停一下”,用 Thread.sleep()
  • 如果你需要让多个线程互相配合、互相等待,用 Object.wait()Object.notify()/notifyAll()
分享:
扫描分享到社交APP
上一篇
下一篇