Java线程间通信与同步:深入解析notify与wait方法

152 阅读5分钟

notify和 wait 详细介绍notify和wait的详细介绍如下:

一、notify

1. 基本概念

notify是Java中的一个方法,属于Object类,用于唤醒正在等待该对象监视器的单个线程。当某个线程调用了某个对象的wait()方法后,该线程会进入等待状态并释放该对象的锁,直到其他线程调用此对象的notify()方法或者notifyAll()方法,或者等待超时,该线程才会重新获得锁并进入就绪状态。

2. 使用场景

notify方法通常用于线程间的通信,特别是在生产者-消费者模型中。生产者线程在生产出数据后,通过调用notify方法唤醒等待在该对象上的消费者线程,使其能够继续执行并处理数据。

3. 注意事项

  • notify方法必须在同步代码块或同步方法中调用,且必须持有调用该方法的对象的锁。
  • notify方法一次只能唤醒一个等待线程,如果有多个线程在等待,那么唤醒哪个线程是不确定的。如果需要唤醒所有等待线程,应使用notifyAll方法。
  • 被唤醒的线程不会立即执行,而是会进入就绪状态,等待CPU的调度。

二、wait

1. 基本概念

wait也是Java中的一个方法,同样属于Object类,用于使当前线程等待,直到其他线程调用此对象的notify()方法或notifyAll()方法,或者等待的时间超过指定的时间长度。调用wait方法会释放当前线程持有的对象的锁,并导致当前线程进入等待状态。

2. 使用场景

wait方法通常用于线程间的同步控制,特别是在需要等待某个条件成立的场景中。例如,在消费者线程中,如果队列为空,消费者线程可以通过调用wait方法进入等待状态,直到生产者线程生产了数据并调用notify或notifyAll方法唤醒它。

3. 注意事项

  • wait方法必须在同步代码块或同步方法中调用,且必须持有调用该方法的对象的锁。
  • 调用wait方法会释放当前线程持有的锁,这是wait和sleep方法的一个重要区别。
  • wait方法可以通过传递参数来指定等待的时间,如果时间到了还没有被唤醒,线程会自动醒来并重新尝试获取锁。
  • 在等待过程中,如果线程被中断,那么wait方法会抛出InterruptedException异常。

综上所述,notify和wait是Java中用于线程间通信和同步控制的重要方法,它们通过对象的监视器锁来实现线程间的协调与配合。在使用时需要注意它们的使用场景和注意事项,以确保程序的正确性和高效性。以下通过示例来详细讲解Java中的notifywait方法:

示例背景

假设我们有一个简单的生产者-消费者问题,其中生产者线程负责生成数据并将其放入缓冲区,消费者线程从缓冲区中取出数据进行处理。为了同步生产者和消费者的操作,我们可以使用waitnotify方法。

示例代码

public class ProducerConsumerExample {
    private List<Integer> buffer = new ArrayList<>();
    private int capacity = 5; // 缓冲区容量

    // 生产者方法
    public synchronized void produce(int value) throws InterruptedException {
        while (buffer.size() == capacity) {
            wait(); // 缓冲区满时,生产者线程等待
        }
        buffer.add(value);
        System.out.println("Produced: " + value);
        notify(); // 唤醒在缓冲区上等待的消费者线程
    }

    // 消费者方法
    public synchronized void consume() throws InterruptedException {
        while (buffer.isEmpty()) {
            wait(); // 缓冲区空时,消费者线程等待
        }
        int value = buffer.remove(0);
        System.out.println("Consumed: " + value);
        notify(); // 唤醒在缓冲区上等待的生产者线程(尽管在此示例中可能不是必需的,但为了演示目的包含)
    }

    public static void main(String[] args) {
        ProducerConsumerExample pc = new ProducerConsumerExample();

        // 生产者线程
        Thread producer = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    pc.produce(i);
                    Thread.sleep(100); // 模拟生产耗时
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // 消费者线程
        Thread consumer = new Thread(() -> {
            try {
                while (true) {
                    pc.consume();
                    Thread.sleep(200); // 模拟消费耗时
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        producer.start();
        consumer.start();
    }
}

讲解

  1. synchronized关键字

    • produceconsume方法都被声明为synchronized,这意味着在同一时刻,只有一个线程可以执行这些方法中的任何一个。这保证了线程安全,防止多个线程同时访问缓冲区导致数据不一致。
  2. wait方法

    • 当缓冲区满时(buffer.size() == capacity),生产者线程调用wait()方法进入等待状态,并释放缓冲区对象的锁。此时,生产者线程暂停执行,直到其他线程调用缓冲区对象的notify()notifyAll()方法。
    • 类似地,当缓冲区空时(buffer.isEmpty()),消费者线程也会调用wait()方法等待。
  3. notify方法

    • 当生产者线程向缓冲区中添加数据后,它会调用notify()方法来唤醒可能正在缓冲区对象上等待的消费者线程。注意,notify()方法只会唤醒一个等待线程,如果有多个线程在等待,那么唤醒哪个线程是不确定的。
    • 在这个示例中,消费者线程在消费数据后也调用了notify()方法,尽管这在实际的生产者-消费者模型中可能不是必需的,因为消费者通常不需要主动唤醒生产者(除非有特定的同步需求)。但这里为了演示notify()方法的使用而包含。
  4. 唤醒与竞争锁

    • notify()唤醒的线程不会立即执行,而是会进入就绪状态,等待CPU的调度。当该线程获得CPU时间片时,它会尝试重新获取之前释放的锁。如果此时锁仍然被其他线程持有,那么该线程将再次进入阻塞状态,直到锁被释放。
    • 一旦线程成功获取锁,它将从wait()方法之后的位置继续执行。

通过这个示例,我们可以看到notifywait方法在多线程编程中的重要作用,它们是实现线程间通信和同步控制的关键机制。

欢迎访问我的公众号或博客(点击查看头像信息)