跳转至

Java 中的 wait 和 notify 机制

简介

在多线程编程中,线程之间的协作是一个关键问题。Java 提供了 wait()notify() 方法来实现线程间的通信与协作。通过这两个方法,线程可以在特定条件下暂停执行(wait),并在条件满足时被其他线程唤醒(notify)。这篇博客将深入探讨 waitnotify 在 Java 中的使用。

目录

  1. 基础概念
  2. 使用方法
    • wait() 方法
    • notify() 方法
    • notifyAll() 方法
  3. 常见实践
    • 生产者 - 消费者模型
  4. 最佳实践
  5. 小结
  6. 参考资料

基础概念

wait()notify()notifyAll()Object 类的方法,这意味着 Java 中的每个对象都有这三个方法。它们主要用于线程间的同步和通信。

  • wait():当一个线程调用对象的 wait() 方法时,该线程会释放对象的锁,并进入等待状态。直到其他线程调用该对象的 notify()notifyAll() 方法将其唤醒。
  • notify():唤醒在此对象监视器上等待的单个线程。如果有多个线程在等待,则选择其中一个唤醒。
  • notifyAll():唤醒在此对象监视器上等待的所有线程。

使用方法

wait() 方法

wait() 方法有三种重载形式: - wait():使当前线程等待,直到另一个线程调用该对象的 notify()notifyAll() 方法。 - wait(long timeout):使当前线程等待,直到另一个线程调用该对象的 notify()notifyAll() 方法,或者指定的时间(以毫秒为单位)过去。 - wait(long timeout, int nanos):与上一个类似,但增加了纳秒级别的精度。

public class WaitExample {
    public static void main(String[] args) {
        Object lock = new Object();

        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread 1: Starting...");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 1: Resumed...");
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread 2: Starting...");
                lock.notify();
                System.out.println("Thread 2: Notified...");
            }
        });

        thread1.start();
        thread2.start();
    }
}

在这个例子中,thread1 调用 lock.wait() 后进入等待状态并释放 lock 的锁。thread2 获得锁后调用 lock.notify() 唤醒 thread1

notify() 方法

notify() 方法用于唤醒在此对象监视器上等待的单个线程。

public class NotifyExample {
    public static void main(String[] args) {
        Object lock = new Object();

        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread 1: Starting...");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 1: Resumed...");
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread 2: Starting...");
                lock.notify();
                System.out.println("Thread 2: Notified...");
            }
        });

        thread1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
    }
}

在这个例子中,thread2 调用 lock.notify() 唤醒了 thread1

notifyAll() 方法

notifyAll() 方法用于唤醒在此对象监视器上等待的所有线程。

public class NotifyAllExample {
    public static void main(String[] args) {
        Object lock = new Object();

        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread 1: Starting...");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 1: Resumed...");
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread 2: Starting...");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 2: Resumed...");
            }
        });

        Thread thread3 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread 3: Starting...");
                lock.notifyAll();
                System.out.println("Thread 3: Notified all...");
            }
        });

        thread1.start();
        thread2.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread3.start();
    }
}

在这个例子中,thread3 调用 lock.notifyAll() 唤醒了 thread1thread2

常见实践:生产者 - 消费者模型

生产者 - 消费者模型是多线程编程中的经典问题,waitnotify 可以很好地解决这个问题。

import java.util.LinkedList;
import java.util.Queue;

class Producer implements Runnable {
    private final Queue<Integer> queue;
    private final int capacity;

    public Producer(Queue<Integer> queue, int capacity) {
        this.queue = queue;
        this.capacity = capacity;
    }

    @Override
    public void run() {
        int value = 0;
        while (true) {
            synchronized (queue) {
                while (queue.size() == capacity) {
                    try {
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                queue.add(value++);
                System.out.println("Produced: " + (value - 1));
                queue.notify();
            }
        }
    }
}

class Consumer implements Runnable {
    private final Queue<Integer> queue;

    public Consumer(Queue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (queue) {
                while (queue.isEmpty()) {
                    try {
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                int value = queue.poll();
                System.out.println("Consumed: " + value);
                queue.notify();
            }
        }
    }
}

public class ProducerConsumerExample {
    public static void main(String[] args) {
        Queue<Integer> queue = new LinkedList<>();
        int capacity = 5;

        Thread producerThread = new Thread(new Producer(queue, capacity));
        Thread consumerThread = new Thread(new Consumer(queue));

        producerThread.start();
        consumerThread.start();
    }
}

在这个例子中,Producer 线程在队列满时调用 queue.wait() 等待,Consumer 线程消费后调用 queue.notify() 唤醒 Producer 线程。反之,Consumer 线程在队列空时等待,Producer 线程生产后唤醒 Consumer 线程。

最佳实践

  • 始终在 synchronized 块中调用 wait()notify()notifyAll():这些方法必须在获取对象的锁之后调用,否则会抛出 IllegalMonitorStateException
  • 使用 while 循环检查条件:被唤醒的线程可能会出现“虚假唤醒”的情况,即没有被其他线程调用 notify()notifyAll() 就被唤醒了。因此,应该在 while 循环中检查等待的条件,确保条件满足后再继续执行。
  • 合理选择 notify()notifyAll():如果只有一个线程在等待并且知道只有一个线程需要被唤醒,使用 notify() 可以提高性能。如果有多个线程在等待并且都可能需要被唤醒,使用 notifyAll()

小结

waitnotify 是 Java 多线程编程中实现线程间通信的重要机制。通过合理使用这两个方法,可以解决诸如生产者 - 消费者模型等多线程协作问题。在使用过程中,需要注意在 synchronized 块中调用,以及处理虚假唤醒等问题,遵循最佳实践可以确保代码的正确性和高效性。

参考资料

希望这篇博客能帮助你深入理解并高效使用 Java 中的 waitnotify 机制。如果你有任何问题或建议,欢迎在评论区留言。