跳转至

Java中的wait()与notify()机制

简介

在多线程编程中,线程之间的协调与通信至关重要。Java提供了wait()notify()方法,用于线程间的同步与通信。这两个方法是Object类的一部分,意味着每个Java对象都自带了这种线程间通信的能力。理解并正确使用wait()notify()能够有效地解决多线程环境下的复杂问题,如生产者-消费者模型。

目录

  1. 基础概念
    • wait()方法
    • notify()方法
    • notifyAll()方法
  2. 使用方法
    • 基本语法
    • 调用条件
  3. 常见实践
    • 生产者-消费者模型
    • 线程间的简单同步
  4. 最佳实践
    • 避免死锁
    • 合理使用wait()的超时机制
    • 确保正确的锁对象
  5. 小结
  6. 参考资料

基础概念

wait()方法

wait()方法用于使当前线程等待,直到其他线程调用该对象的notify()notifyAll()方法。当一个线程调用wait()方法时,它会释放对象的锁,进入等待状态。这是为了避免其他线程因为无法获取锁而一直阻塞。当wait()的线程被唤醒后,它会重新尝试获取对象的锁,然后继续执行wait()方法之后的代码。

notify()方法

notify()方法用于唤醒在此对象监视器上等待的单个线程。如果有多个线程在等待,那么会随机选择一个线程唤醒。被唤醒的线程会从等待状态变为可运行状态,但它不会立即执行,而是需要等待获取对象的锁。

notifyAll()方法

notifyAll()方法会唤醒在此对象监视器上等待的所有线程。所有被唤醒的线程都会竞争对象的锁,最终只有一个线程能够获取锁并继续执行,其他线程会再次进入阻塞状态等待锁的释放。

使用方法

基本语法

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

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

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

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

调用条件

  1. wait()notify()notifyAll()方法必须在synchronized块或方法中调用。这是因为这些方法的调用依赖于对象的监视器(锁),而synchronized块或方法能够确保当前线程拥有对象的锁。
  2. 调用wait()方法的线程必须是持有对象锁的线程。否则,会抛出IllegalMonitorStateException异常。

常见实践

生产者-消费者模型

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 {
                        System.out.println("Queue is full, producer is waiting");
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                queue.add(value++);
                System.out.println("Produced: " + 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 {
                        System.out.println("Queue is empty, consumer is waiting");
                        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();
    }
}

线程间的简单同步

public class SimpleSyncExample {
    private static boolean flag = false;

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

        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                while (!flag) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("Thread 1: flag is now true");
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                flag = true;
                lock.notify();
                System.out.println("Thread 2: set flag to true and notified");
            }
        });

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

最佳实践

避免死锁

死锁是多线程编程中常见的问题,在使用wait()notify()时尤其需要注意。为了避免死锁: 1. 确保所有线程获取锁的顺序一致。 2. 尽量减少锁的持有时间,避免长时间占用锁。 3. 使用wait()的超时机制,防止线程无限期等待。

合理使用wait()的超时机制

wait()方法有一个带参数的重载版本,允许指定等待的最长时间。合理使用这个超时机制可以避免线程因为某些异常情况而一直等待。

synchronized (lock) {
    try {
        lock.wait(5000); // 等待5秒
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

确保正确的锁对象

在调用wait()notify()notifyAll()时,要确保使用的是正确的锁对象。如果多个线程使用不同的锁对象,那么线程间的通信将无法正常进行。

小结

wait()notify()是Java多线程编程中强大的工具,用于线程间的同步与通信。通过合理使用这两个方法,可以解决诸如生产者-消费者模型等复杂的多线程问题。在使用过程中,需要注意基本概念、调用条件以及遵循最佳实践,以避免常见的问题,如死锁。

参考资料

  1. 《Effective Java》 - Joshua Bloch
  2. 《Java Concurrency in Practice》 - Brian Goetz