跳转至

Java 中的 notify() 和 wait():深入理解线程间通信

简介

在多线程编程中,线程之间的有效通信至关重要。Java 提供了 notify()wait() 方法来实现线程间的协调与同步。通过这些方法,线程可以暂停执行、等待特定条件满足,然后由其他线程唤醒继续执行。本文将深入探讨 notify()wait() 的基础概念、使用方法、常见实践以及最佳实践,帮助读者在多线程编程中更好地运用这些机制。

目录

  1. 基础概念
    • wait() 方法
    • notify() 方法
    • notifyAll() 方法
  2. 使用方法
    • 调用 wait()
    • 调用 notify()notifyAll()
  3. 常见实践
    • 生产者 - 消费者模式
    • 线程间条件等待
  4. 最佳实践
    • 避免死锁
    • 合理使用 notify()notifyAll()
    • 正确处理异常
  5. 小结
  6. 参考资料

基础概念

wait() 方法

wait()Object 类的实例方法。当一个线程调用对象的 wait() 方法时,该线程会释放对象的锁,并进入等待状态,直到其他线程调用该对象的 notify()notifyAll() 方法将其唤醒,或者等待时间超时(如果使用了带超时参数的 wait() 方法)。

notify() 方法

notify() 也是 Object 类的实例方法。当一个线程调用对象的 notify() 方法时,它会随机唤醒在此对象监视器(锁)上等待的单个线程。被唤醒的线程将重新竞争获取该对象的锁,获取到锁后继续执行。

notifyAll() 方法

notifyAll() 同样是 Object 类的实例方法。与 notify() 不同,notifyAll() 会唤醒在此对象监视器(锁)上等待的所有线程。这些被唤醒的线程都会竞争获取该对象的锁,最终只有一个线程能获取到锁并继续执行,其他线程将再次进入等待状态。

使用方法

调用 wait()

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

        Thread waitingThread = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("等待线程开始等待...");
                    lock.wait();
                    System.out.println("等待线程被唤醒...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        waitingThread.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Thread notifyingThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("通知线程获取锁并唤醒等待线程...");
                lock.notify();
            }
        });

        notifyingThread.start();
    }
}

在上述代码中,waitingThread 调用 lock.wait() 进入等待状态并释放 lock 的锁。notifyingThread 在延迟 2 秒后启动,获取 lock 的锁并调用 lock.notify() 唤醒 waitingThread

调用 notify()notifyAll()

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

        Thread waitingThread1 = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("等待线程 1 开始等待...");
                    lock.wait();
                    System.out.println("等待线程 1 被唤醒...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread waitingThread2 = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("等待线程 2 开始等待...");
                    lock.wait();
                    System.out.println("等待线程 2 被唤醒...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        waitingThread1.start();
        waitingThread2.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Thread notifyingThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("通知线程获取锁并唤醒所有等待线程...");
                lock.notifyAll();
            }
        });

        notifyingThread.start();
    }
}

此代码中,waitingThread1waitingThread2 都调用 lock.wait() 进入等待状态。notifyingThread 在延迟 2 秒后启动,调用 lock.notifyAll() 唤醒所有等待线程。

常见实践

生产者 - 消费者模式

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("生产: " + value);
                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("消费: " + 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();
    }
}

在生产者 - 消费者模式中,生产者线程生产数据并放入队列,当队列满时生产者线程调用 wait() 等待;消费者线程从队列中消费数据,当队列空时消费者线程调用 wait() 等待。当有新数据放入或取出时,调用 notify() 唤醒对方线程。

线程间条件等待

public class ConditionWaitExample {
    private static boolean conditionMet = false;

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

        Thread waitingThread = new Thread(() -> {
            synchronized (lock) {
                while (!conditionMet) {
                    try {
                        System.out.println("等待线程开始等待条件满足...");
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("等待线程条件满足,继续执行...");
            }
        });

        waitingThread.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Thread notifyingThread = new Thread(() -> {
            synchronized (lock) {
                conditionMet = true;
                System.out.println("通知线程设置条件为真并唤醒等待线程...");
                lock.notify();
            }
        });

        notifyingThread.start();
    }
}

在这个例子中,waitingThread 等待 conditionMettrue,在条件不满足时调用 wait()notifyingThread 在延迟 2 秒后设置 conditionMettrue 并调用 notify() 唤醒 waitingThread

最佳实践

避免死锁

死锁是多线程编程中常见的问题,通常由于线程相互等待对方释放资源而导致。为避免死锁,应遵循以下原则: - 尽量减少锁的持有时间,避免长时间持有锁而不释放。 - 按照固定顺序获取锁,避免交叉获取锁。 - 使用定时锁(如 tryLock() 方法),设置合理的等待时间,避免无限期等待。

合理使用 notify()notifyAll()

  • 如果只有一个线程在等待特定条件,使用 notify() 可以提高效率,因为它只唤醒一个线程。
  • 如果有多个线程在等待不同条件,或者无法确定哪个线程需要被唤醒,使用 notifyAll() 确保所有等待线程都有机会竞争锁并检查条件。

正确处理异常

在调用 wait() 方法时,需要捕获 InterruptedException 异常。这是因为在等待过程中,线程可能被中断,正确处理异常可以保证程序的健壮性。

小结

notify()wait() 是 Java 多线程编程中实现线程间通信的重要机制。通过合理运用这些方法,可以实现复杂的多线程协调与同步。在实际应用中,需要注意避免死锁、合理选择 notify()notifyAll() 以及正确处理异常,以确保程序的正确性和高效性。

参考资料

  • 《Effective Java》 - Joshua Bloch
  • 《Java Concurrency in Practice》 - Brian Goetz

希望通过本文的介绍,读者能够深入理解并灵活运用 notify()wait() 方法,提升多线程编程的能力。