在 Java 中,wait() 和 sleep() 都是让线程进入阻塞状态的方法,但是它们的用途、行为和线程安全性有所不同。下面详细解释这两个方法的区别以及它们在线程安全性方面的影响。

1. wait() 和 sleep() 的区别

wait()

  • 用途wait() 方法是 Java 中 Object 类的一部分,用于线程间的通信。它使当前线程进入等待状态,直到某个条件被满足或者被其他线程唤醒。
  • 调用位置wait() 方法必须在同步代码块或者同步方法中调用,即它必须是在一个对象锁的持有者中调用的。通常,配合 notify() 或 notifyAll() 方法使用,通知正在等待的线程。
  • 行为
    • 使当前线程释放锁并进入等待队列,直到其他线程调用相同对象的 notify() 或 notifyAll() 来唤醒它。
    • 可以指定等待时间:wait(long timeout) 使线程等待指定的时间(毫秒)。
    • 释放对象的锁:调用 wait() 后,线程释放它所持有的锁,这允许其他线程获得该锁。

sleep()

  • 用途sleep() 方法是 Thread 类的一部分,用于让当前线程进入睡眠状态,使线程暂停执行一定的时间。与线程间通信无关,单纯地让线程暂停。
  • 调用位置sleep() 方法是静态方法,可以在任何地方调用,而无需持有锁。
  • 行为
    • 使当前线程暂停指定时间(以毫秒为单位)。Thread.sleep(long millis) 会使线程暂停指定的毫秒数。
    • 不释放对象的锁:sleep() 不会释放当前持有的锁,调用时仍然持有锁,直到线程睡眠结束后恢复执行。

主要区别

特性wait()sleep()
所属类Object 类Thread 类
调用方式必须在同步块中调用(持有对象锁)可以在任何地方调用
锁的释放释放对象锁不释放锁
用途线程间通信线程暂停
唤醒机制通过 notify() 或 notifyAll() 唤醒线程时间到自动恢复执行
暂停时间可指定等待时间,但不会指定暂停的精确时间精确暂停指定的时间

例子:wait() 和 sleep() 的使用

class WaitExample {
    private static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        // 使用 wait()
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("Thread 1: Waiting...");
                    lock.wait();  // 释放锁并进入等待状态
                    System.out.println("Thread 1: Woken up!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread 2: Doing some work...");
                lock.notify();  // 唤醒线程 t1
                System.out.println("Thread 2: Notify done!");
            }
        });

        t1.start();
        t2.start();
    }
}

class SleepExample {
    public static void main(String[] args) throws InterruptedException {
        // 使用 sleep()
        Thread t1 = new Thread(() -> {
            try {
                System.out.println("Thread 1: Sleeping for 2 seconds...");
                Thread.sleep(2000);  // 暂停线程 2 秒
                System.out.println("Thread 1: Woke up!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t1.start();
    }
}

2. 线程安全性问题

wait() 和 线程安全性

  • wait() 方法是设计用来在线程间进行协调与通信的,因此它的线程安全性在于它是和同步机制一起使用的。使用 wait() 的线程必须持有对象锁,否则会抛出 IllegalMonitorStateException
  • 由于 wait() 需要配合 notify() 或 notifyAll() 使用,因此它通常与多线程共享的资源状态相关联。在这种情况下,线程安全性通常通过同步(如 synchronized)来保证,即确保只有一个线程能进入临界区修改共享资源的状态。

sleep() 和 线程安全性

  • sleep() 方法本身不涉及线程之间的通信,它只是让当前线程暂停一段时间。在 sleep() 期间,线程不会与其他线程共享锁,因此 不涉及线程安全性 的问题。
  • sleep() 不会释放对象锁,这意味着如果 sleep() 是在同步方法或代码块中调用的,其他线程在当前线程睡眠时依然无法获得该锁。这种行为可能导致线程在等待期间长时间无法执行,影响线程的响应性和系统的并发性。

线程安全性总结

  • wait():必须在同步代码块中使用,释放锁后等待被唤醒,适用于线程间协调。使用时需要小心 死锁 和 资源竞争 问题。
  • sleep():不会释放锁,适用于让线程暂停执行。由于它不涉及线程间通信,所以不直接影响线程安全,但可能影响线程的调度与响应性。

3. 实际应用场景

  • wait():常用于生产者-消费者问题、读写锁等需要线程之间协调的场景。例如,一个线程等待某些条件满足时进入等待状态,另一个线程更新条件并通知等待线程继续执行。
  • sleep():通常用于调度线程的执行,或者在某些任务之间加入延迟。例如,模拟定时任务、进行任务间隔控制等。

4. 总结

  • wait() 和 sleep() 都可以让线程进入阻塞状态,但 wait() 是用来实现线程间通信的,需要在同步块中调用,并释放锁,等待被唤醒。 sleep() 用来让线程暂停一段时间,它不会释放锁。
  • 线程安全性wait() 在使用时需要注意同步控制和线程间通信的正确性,而 sleep() 主要影响线程的调度和响应性,不会直接影响线程安全性,但如果在同步代码块中使用,可能会导致锁长时间未释放,影响系统性能。