Java如何使用wait/notify实现线程间通信

发布时间:2022-12-13 09:08:46 作者:iii
来源:亿速云 阅读:168

Java如何使用wait/notify实现线程间通信

目录

  1. 引言
  2. 线程间通信的基本概念
  3. wait/notify机制概述
  4. wait方法详解
  5. notify方法详解
  6. notifyAll方法详解
  7. wait/notify的使用场景
  8. wait/notify的注意事项
  9. wait/notify的经典示例
  10. wait/notify与锁的关系
  11. wait/notify与Condition的对比
  12. wait/notify的性能考虑
  13. wait/notify的常见问题与解决方案
  14. 总结

引言

在多线程编程中,线程间的通信是一个非常重要的概念。Java提供了多种机制来实现线程间的通信,其中waitnotify是最基础也是最常用的机制之一。本文将详细介绍如何使用waitnotify来实现线程间的通信,并通过示例代码和实际应用场景来帮助读者更好地理解这一机制。

线程间通信的基本概念

在多线程环境中,线程之间的通信是指线程之间通过某种机制来交换信息或协调工作。线程间通信的主要目的是为了实现线程之间的同步,确保多个线程能够按照预期的顺序执行。

在Java中,线程间通信可以通过以下几种方式实现:

  1. 共享变量:多个线程通过共享变量来交换信息。
  2. wait/notify机制:通过waitnotify方法来实现线程的等待和唤醒。
  3. Lock/Condition机制:通过LockCondition接口来实现更灵活的线程间通信。
  4. 阻塞队列:通过BlockingQueue来实现线程间的数据交换。

本文将重点介绍waitnotify机制。

wait/notify机制概述

waitnotify是Java中用于线程间通信的两个重要方法,它们定义在Object类中。wait方法用于使当前线程进入等待状态,直到其他线程调用notifynotifyAll方法来唤醒它。notify方法用于唤醒一个正在等待的线程,而notifyAll方法则用于唤醒所有正在等待的线程。

waitnotify方法必须在同步代码块或同步方法中调用,因为它们依赖于对象的监视器锁(monitor lock)。调用wait方法时,当前线程会释放它所持有的锁,并进入等待状态。当其他线程调用notifynotifyAll方法时,等待的线程会被唤醒,并重新尝试获取锁。

wait方法详解

wait方法用于使当前线程进入等待状态,直到其他线程调用notifynotifyAll方法来唤醒它。wait方法有以下几个重载版本:

wait方法的使用

wait方法必须在同步代码块或同步方法中调用,否则会抛出IllegalMonitorStateException异常。调用wait方法时,当前线程会释放它所持有的锁,并进入等待状态。当其他线程调用notifynotifyAll方法时,等待的线程会被唤醒,并重新尝试获取锁。

synchronized (obj) {
    while (condition) {
        obj.wait();
    }
    // 执行其他操作
}

在上面的代码中,obj是一个共享对象,condition是一个条件变量。当conditiontrue时,当前线程会进入等待状态,直到其他线程调用notifynotifyAll方法。

notify方法详解

notify方法用于唤醒一个正在等待的线程。notify方法必须在同步代码块或同步方法中调用,否则会抛出IllegalMonitorStateException异常。调用notify方法时,当前线程会唤醒一个正在等待的线程,但具体唤醒哪个线程是由JVM决定的。

synchronized (obj) {
    // 修改条件变量
    condition = false;
    obj.notify();
}

在上面的代码中,obj是一个共享对象,condition是一个条件变量。当condition被修改为false时,当前线程会调用notify方法来唤醒一个正在等待的线程。

notifyAll方法详解

notifyAll方法用于唤醒所有正在等待的线程。notifyAll方法必须在同步代码块或同步方法中调用,否则会抛出IllegalMonitorStateException异常。调用notifyAll方法时,当前线程会唤醒所有正在等待的线程。

synchronized (obj) {
    // 修改条件变量
    condition = false;
    obj.notifyAll();
}

在上面的代码中,obj是一个共享对象,condition是一个条件变量。当condition被修改为false时,当前线程会调用notifyAll方法来唤醒所有正在等待的线程。

wait/notify的使用场景

waitnotify机制通常用于实现生产者-消费者模型、线程池、任务调度等场景。在这些场景中,多个线程需要共享资源或协调工作,waitnotify机制可以帮助我们实现线程间的同步和通信。

生产者-消费者模型

生产者-消费者模型是一个经典的多线程问题,其中生产者线程负责生成数据,消费者线程负责消费数据。waitnotify机制可以用于实现生产者-消费者模型中的线程同步。

class Buffer {
    private Queue<Integer> queue = new LinkedList<>();
    private int capacity;

    public Buffer(int capacity) {
        this.capacity = capacity;
    }

    public synchronized void produce(int value) throws InterruptedException {
        while (queue.size() == capacity) {
            wait();
        }
        queue.add(value);
        notifyAll();
    }

    public synchronized int consume() throws InterruptedException {
        while (queue.isEmpty()) {
            wait();
        }
        int value = queue.poll();
        notifyAll();
        return value;
    }
}

在上面的代码中,Buffer类是一个缓冲区,produce方法用于生产数据,consume方法用于消费数据。当缓冲区满时,生产者线程会进入等待状态;当缓冲区为空时,消费者线程会进入等待状态。当数据被生产或消费时,线程会调用notifyAll方法来唤醒其他线程。

线程池

线程池是一种常见的多线程编程模式,它通过维护一组线程来执行任务。waitnotify机制可以用于实现线程池中的任务调度。

class ThreadPool {
    private Queue<Runnable> taskQueue = new LinkedList<>();
    private List<WorkerThread> threads = new ArrayList<>();
    private boolean isShutdown = false;

    public ThreadPool(int poolSize) {
        for (int i = 0; i < poolSize; i++) {
            WorkerThread thread = new WorkerThread();
            threads.add(thread);
            thread.start();
        }
    }

    public synchronized void execute(Runnable task) {
        if (isShutdown) {
            throw new IllegalStateException("ThreadPool is shutdown");
        }
        taskQueue.add(task);
        notifyAll();
    }

    public synchronized void shutdown() {
        isShutdown = true;
        notifyAll();
    }

    private class WorkerThread extends Thread {
        public void run() {
            while (true) {
                Runnable task;
                synchronized (ThreadPool.this) {
                    while (taskQueue.isEmpty() && !isShutdown) {
                        try {
                            ThreadPool.this.wait();
                        } catch (InterruptedException e) {
                            // 处理中断
                        }
                    }
                    if (isShutdown && taskQueue.isEmpty()) {
                        break;
                    }
                    task = taskQueue.poll();
                }
                task.run();
            }
        }
    }
}

在上面的代码中,ThreadPool类是一个线程池,execute方法用于提交任务,shutdown方法用于关闭线程池。当任务队列为空时,工作线程会进入等待状态;当有新任务提交时,线程池会调用notifyAll方法来唤醒工作线程。

wait/notify的注意事项

在使用waitnotify机制时,需要注意以下几点:

  1. 必须在同步代码块或同步方法中调用waitnotify方法必须在同步代码块或同步方法中调用,否则会抛出IllegalMonitorStateException异常。
  2. 使用while循环检查条件:在调用wait方法时,应该使用while循环来检查条件,而不是if语句。这是因为线程被唤醒后,条件可能仍然不满足,因此需要重新检查条件。
  3. 避免虚假唤醒:虚假唤醒是指线程在没有被notifynotifyAll唤醒的情况下,从wait状态中返回。为了避免虚假唤醒,应该在while循环中检查条件。
  4. 注意锁的释放和获取:调用wait方法时,当前线程会释放它所持有的锁;当线程被唤醒时,它会重新尝试获取锁。因此,在使用waitnotify机制时,需要注意锁的释放和获取。

wait/notify的经典示例

生产者-消费者模型

class Buffer {
    private Queue<Integer> queue = new LinkedList<>();
    private int capacity;

    public Buffer(int capacity) {
        this.capacity = capacity;
    }

    public synchronized void produce(int value) throws InterruptedException {
        while (queue.size() == capacity) {
            wait();
        }
        queue.add(value);
        notifyAll();
    }

    public synchronized int consume() throws InterruptedException {
        while (queue.isEmpty()) {
            wait();
        }
        int value = queue.poll();
        notifyAll();
        return value;
    }
}

class Producer implements Runnable {
    private Buffer buffer;

    public Producer(Buffer buffer) {
        this.buffer = buffer;
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                buffer.produce(i);
                System.out.println("Produced: " + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Consumer implements Runnable {
    private Buffer buffer;

    public Consumer(Buffer buffer) {
        this.buffer = buffer;
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                int value = buffer.consume();
                System.out.println("Consumed: " + value);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ProducerConsumerExample {
    public static void main(String[] args) {
        Buffer buffer = new Buffer(5);
        Thread producerThread = new Thread(new Producer(buffer));
        Thread consumerThread = new Thread(new Consumer(buffer));
        producerThread.start();
        consumerThread.start();
    }
}

在上面的代码中,Buffer类是一个缓冲区,Producer类是一个生产者线程,Consumer类是一个消费者线程。生产者线程负责生成数据并将其放入缓冲区,消费者线程负责从缓冲区中取出数据并消费。当缓冲区满时,生产者线程会进入等待状态;当缓冲区为空时,消费者线程会进入等待状态。当数据被生产或消费时,线程会调用notifyAll方法来唤醒其他线程。

线程池

class ThreadPool {
    private Queue<Runnable> taskQueue = new LinkedList<>();
    private List<WorkerThread> threads = new ArrayList<>();
    private boolean isShutdown = false;

    public ThreadPool(int poolSize) {
        for (int i = 0; i < poolSize; i++) {
            WorkerThread thread = new WorkerThread();
            threads.add(thread);
            thread.start();
        }
    }

    public synchronized void execute(Runnable task) {
        if (isShutdown) {
            throw new IllegalStateException("ThreadPool is shutdown");
        }
        taskQueue.add(task);
        notifyAll();
    }

    public synchronized void shutdown() {
        isShutdown = true;
        notifyAll();
    }

    private class WorkerThread extends Thread {
        public void run() {
            while (true) {
                Runnable task;
                synchronized (ThreadPool.this) {
                    while (taskQueue.isEmpty() && !isShutdown) {
                        try {
                            ThreadPool.this.wait();
                        } catch (InterruptedException e) {
                            // 处理中断
                        }
                    }
                    if (isShutdown && taskQueue.isEmpty()) {
                        break;
                    }
                    task = taskQueue.poll();
                }
                task.run();
            }
        }
    }
}

class Task implements Runnable {
    private int taskId;

    public Task(int taskId) {
        this.taskId = taskId;
    }

    public void run() {
        System.out.println("Task " + taskId + " is running");
    }
}

public class ThreadPoolExample {
    public static void main(String[] args) {
        ThreadPool threadPool = new ThreadPool(3);
        for (int i = 0; i < 10; i++) {
            threadPool.execute(new Task(i));
        }
        threadPool.shutdown();
    }
}

在上面的代码中,ThreadPool类是一个线程池,Task类是一个任务。线程池维护一组工作线程,工作线程从任务队列中取出任务并执行。当任务队列为空时,工作线程会进入等待状态;当有新任务提交时,线程池会调用notifyAll方法来唤醒工作线程。

wait/notify与锁的关系

waitnotify机制依赖于对象的监视器锁(monitor lock)。调用wait方法时,当前线程会释放它所持有的锁,并进入等待状态。当其他线程调用notifynotifyAll方法时,等待的线程会被唤醒,并重新尝试获取锁。

在Java中,每个对象都有一个监视器锁,可以通过synchronized关键字来获取和释放锁。waitnotify方法必须在同步代码块或同步方法中调用,因为它们依赖于对象的监视器锁。

wait/notify与Condition的对比

Condition是Java 5引入的一个接口,它提供了与waitnotify类似的功能,但更加灵活。Condition接口可以与Lock接口一起使用,提供了更细粒度的线程间通信机制。

waitnotify相比,Condition的主要优势在于:

  1. 多个等待队列Condition可以创建多个等待队列,每个队列可以等待不同的条件。而waitnotify只能有一个等待队列。
  2. 更灵活的唤醒机制Condition提供了signalsignalAll方法,可以分别唤醒一个或所有等待的线程。而notify只能唤醒一个等待的线程,notifyAll会唤醒所有等待的线程。
  3. 可中断的等待Condition提供了awaitUninterruptibly方法,可以在等待时忽略中断。而wait方法在等待时会被中断。
class Buffer {
    private Queue<Integer> queue = new LinkedList<>();
    private int capacity;
    private Lock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();

    public Buffer(int capacity) {
        this.capacity = capacity;
    }

    public void produce(int value) throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == capacity) {
                notFull.await();
            }
            queue.add(value);
            notEmpty.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public int consume() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                notEmpty.await();
            }
            int value = queue.poll();
            notFull.signalAll();
            return value;
        } finally {
            lock.unlock();
        }
    }
}

在上面的代码中,Buffer类使用LockCondition来实现生产者-消费者模型。notFull条件用于等待缓冲区不满,notEmpty条件用于等待缓冲区不空。与waitnotify相比,Condition提供了更灵活的线程间通信机制。

wait/notify的性能考虑

在使用waitnotify机制时,需要注意性能问题。waitnotify机制依赖于对象的监视器锁,因此在多线程环境中,锁的竞争可能会成为性能瓶颈。

为了提高性能,可以考虑以下几点:

  1. 减少锁的持有时间:尽量减少同步代码块的范围,避免在同步代码块中执行耗时操作。
  2. 使用notify而不是notifyAllnotify方法只会唤醒一个等待的线程,而notifyAll方法会唤醒所有等待的线程。如果只需要唤醒一个线程,应该使用notify方法。
  3. 使用Condition代替waitnotifyCondition提供了更灵活的线程间通信机制,可以减少锁的竞争。

wait/notify的常见问题与解决方案

虚假唤醒

虚假唤醒是指线程在没有被notifynotifyAll唤醒的情况下,从wait状态中返回。为了避免虚假唤醒,应该在while循环中检查条件。

synchronized (obj) {
    while (condition) {
        obj.wait();
    }
    // 执行其他操作
}

死锁

死锁是指多个线程相互等待对方释放锁,导致所有线程都无法继续执行。为了避免死锁,应该避免嵌套锁,并尽量按照相同的顺序获取锁。

线程饥饿

线程饥饿是指某些线程长时间无法获取锁,导致它们无法执行。为了避免线程饥饿,应该尽量减少锁的持有时间,并使用公平锁。

总结

waitnotify是Java中用于线程间通信的两个重要方法,它们依赖于对象的监视器锁。wait方法用于使当前线程进入等待状态,直到其他线程调用notifynotifyAll方法来唤醒它。notify方法用于唤醒一个正在等待的线程,而`notifyAll

推荐阅读:
  1. java命名规范、命名格式是什么
  2. 反射机制在Java中的作用有哪些

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java wait notify

上一篇:Flutter runApp GestureBinding如何使用

下一篇:vue基于el-table怎么实现多页多选及翻页回显

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》