java并发编程中Monitor里的waitSet和EntryList都是做什么的,两者之间的区别,你可以说出来吗?

背景

在并发编程学习中,学习的时候认识到monitor中有owner、waitSet、entryList,了解到了entrylist是锁竞争失败后的等待队列,但是waitset还没有开始学习,不太清楚,于是搜索了下 waitset是什么,有什么用,和entrylist的区别,以下是整理的结果

Monitor里的waitSet和EntryList都是做什么的,两者之间的区别

在Java的并发编程中,Monitor(监视器)是实现同步和线程间通信的核心机制之一。Monitor 内部有两个重要的队列:等待集(Wait Set)入口列表(Entry List) 。这两个队列分别用于不同的目的,理解它们的区别对于正确使用 synchronized 关键字以及 wait()notify()notifyAll() 方法非常重要。

1. 等待集(Wait Set)

定义:

  • 等待集(Wait Set) 是一个包含所有调用 wait() 方法后被挂起的线程的集合。当线程调用 wait() 时,它会释放当前持有的锁,并进入 Wait Set,暂时挂起自己,直到另一个线程调用 notify()notifyAll() 方法来唤醒它。

工作原理:

  1. 线程进入 Wait Set

    • 当线程调用 wait() 时,它会释放当前持有的锁,并进入 Wait Set,暂时挂起自己。此时,线程不会参与对锁的竞争,也不会消耗CPU资源。
  2. 线程被唤醒

    • 当另一个线程调用 notify()notifyAll() 时,Monitor 会选择一个或多个等待的线程从 Wait Set 中移除,并将它们标记为可运行状态。被唤醒的线程会重新竞争锁,只有成功获取锁的线程才能继续执行。
  3. 线程重新获取锁

    • 被唤醒的线程在重新获取锁之前不会继续执行。这是因为 wait() 方法在返回时会自动重新获取锁,以确保线程在进入临界区时仍然持有锁。

使用场景:

  • 条件等待 :线程可以通过 wait() 方法进入 Wait Set,并在满足某个条件之前暂停执行。这通常用于生产者-消费者模式、读写锁等场景,其中线程需要等待某些资源或条件发生变化。
  • 线程协作 :通过 notify()notifyAll(),线程可以通知其他等待的线程继续执行。这使得多个线程可以协调工作,确保它们在适当的时间点进行交互。

示例:

java 复制代码
synchronized (lock) {
    while (!condition) {
        lock.wait();  // 线程进入 Wait Set,等待条件满足
    }
    // 条件满足后继续执行
}

2. 入口列表(Entry List)

定义:

  • 入口列表(Entry List) 是一个包含所有试图获取锁但未能成功获取的线程的集合。当多个线程尝试进入同一个 synchronized 块或方法时,只有一个线程能够成功获取锁并进入临界区,其他线程会被阻塞并放入 Entry List,等待锁的释放。

工作原理:

  1. 线程进入 Entry List

    • 当多个线程同时尝试获取同一个对象的锁时,只有一个线程能够成功获取锁并进入临界区,其他线程会被阻塞并放入 Entry List。这些线程会在 Entry List 中排队,等待锁的释放。
  2. 线程获取锁

    • 当持有锁的线程退出 synchronized 块或方法时,它会释放锁。此时,Monitor 会从 Entry List 中选择一个线程,允许它获取锁并继续执行。选择的顺序通常是FIFO(先进先出),但JVM的具体实现可能会有所不同。
  3. 线程竞争锁

    • 如果有多个线程在 Entry List 中等待,Monitor 会根据某种调度算法(如FIFO或优先级调度)选择一个线程来获取锁。被选中的线程会从 Entry List 中移除,并尝试获取锁。如果获取成功,该线程可以进入临界区;否则,它会继续等待。

使用场景:

  • 锁竞争 :当多个线程尝试进入同一个 synchronized 块或方法时,Entry List 用于管理这些线程的排队和锁的分配。它确保在同一时间只有一个线程能够持有锁并进入临界区,从而避免数据竞争和不一致的问题。
  • 线程调度Entry List 还负责管理线程的调度,确保线程在适当的时机获取锁并继续执行。

示例:

java 复制代码
synchronized (lock) {
    // 只有一个线程能够进入这个临界区,其他线程会被放入 Entry List
    // 等待锁的释放
}

3. Wait SetEntry List 的区别

特性 等待集(Wait Set) 入口列表(Entry List)
定义 包含所有调用 wait() 后被挂起的线程 包含所有试图获取锁但未能成功获取的线程
线程状态 线程已经获取了锁,但在 wait() 后释放了锁并进入等待状态 线程尚未获取锁,正在等待锁的释放
进入条件 线程调用 wait() 后进入 Wait Set 线程尝试获取锁但未能成功时进入 Entry List
唤醒方式 通过 notify()notifyAll() 唤醒 通过锁的释放(即持有锁的线程退出 synchronized 块)唤醒
重新获取锁 被唤醒的线程需要重新竞争锁 线程从 Entry List 中被选中后直接获取锁
典型使用场景 条件等待、线程协作 锁竞争、线程调度
示例方法 wait()notify()notifyAll() synchronized 块或方法

4. 示例:Wait SetEntry List 的结合使用

以下是一个简单的生产者-消费者问题的示例,展示了 Wait SetEntry List 的结合使用:

java 复制代码
public class ProducerConsumerExample {

    private static final Object lock = new Object();
    private static final Queue<Integer> buffer = new LinkedList<>();
    private static final int MAX_SIZE = 10;

    public static void main(String[] args) {
        Thread producer = new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                produce(i);
            }
        });

        Thread consumer = new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                consume();
            }
        });

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

    private static void produce(int value) {
        synchronized (lock) {
            // 如果缓冲区已满,生产者进入 Wait Set,等待消费者消费
            while (buffer.size() == MAX_SIZE) {
                try {
                    System.out.println("Buffer is full, producer is waiting...");
                    lock.wait();  // 生产者进入 Wait Set
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            buffer.add(value);
            System.out.println("Produced: " + value);
            lock.notify();  // 唤醒一个等待的消费者
        }
    }

    private static void consume() {
        synchronized (lock) {
            // 如果缓冲区为空,消费者进入 Wait Set,等待生产者生产
            while (buffer.isEmpty()) {
                try {
                    System.out.println("Buffer is empty, consumer is waiting...");
                    lock.wait();  // 消费者进入 Wait Set
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            int value = buffer.remove();
            System.out.println("Consumed: " + value);
            lock.notify();  // 唤醒一个等待的生产者
        }
    }
}

在这个示例中:

  • Wait Set :生产者和消费者通过 wait()notify()Wait Set 中协作。生产者在缓冲区满时进入 Wait Set,等待消费者消费;消费者在缓冲区空时进入 Wait Set,等待生产者生产。
  • Entry List :当多个生产者或消费者尝试进入 synchronized 块时,未能获取锁的线程会被放入 Entry List,等待锁的释放。只有成功获取锁的线程才能进入临界区进行生产和消费操作。

5. 总结

  • Wait Set :用于线程的条件等待和协作。线程在调用 wait() 后进入 Wait Set,并在满足条件时通过 notify()notifyAll() 被唤醒。被唤醒的线程需要重新竞争锁。
  • Entry List :用于管理锁的竞争。当多个线程尝试获取同一个锁时,未能成功获取锁的线程会被放入 Entry List,等待锁的释放。锁释放后,Monitor 会从 Entry List 中选择一个线程来获取锁并继续执行。

理解 Wait SetEntry List 的区别和作用,可以帮助你更好地设计和实现高效的多线程程序,特别是在需要线程协作和锁管理的场景中。

waitSet详解

在Java的并发编程中,Monitor(监视器)是实现同步和线程间通信的核心机制之一。Monitor不仅负责管理锁的状态,还提供了一种线程间的协作机制,即等待集(Wait Set)Wait SetMonitor中的一个重要组成部分,用于支持线程之间的条件等待和通知操作。

1. 什么是 Wait Set

Wait Set 是一个包含所有正在等待某个特定条件的线程的集合。当一个线程调用对象的 wait() 方法时,该线程会释放当前持有的锁,并进入该对象的 Wait Set,暂时挂起自己,直到另一个线程调用 notify()notifyAll() 方法来唤醒它。

  • wait() :线程调用 wait() 后,会释放当前持有的锁,并进入 Wait Set,等待其他线程的通知。
  • notify() :线程调用 notify() 后,会从 Wait Set 中随机选择一个等待的线程并唤醒它。
  • notifyAll() :线程调用 notifyAll() 后,会唤醒 Wait Set 中所有等待的线程。

2. Wait Set 的作用

Wait Set 的主要作用是允许线程在满足某些条件之前暂时挂起自己,避免不必要的忙等待(Busy Waiting),从而提高系统的效率和响应性。具体来说,Wait Set 有以下几个重要的功能:

2.1 条件等待

线程可以通过 wait() 方法进入 Wait Set,并在满足某个条件之前暂停执行。这通常用于生产者-消费者模式、读写锁等场景,其中线程需要等待某些资源或条件发生变化。

例如,在生产者-消费者问题中,消费者线程可能会因为缓冲区为空而调用 wait(),进入 Wait Set,等待生产者线程将数据放入缓冲区后通过 notify() 唤醒它。

java 复制代码
synchronized (buffer) {
    while (buffer.isEmpty()) {
        buffer.wait();  // 消费者线程进入 Wait Set,等待生产者通知
    }
    // 继续消费数据
}

2.2 线程协作

Wait Set 提供了线程之间的协作机制。通过 notify()notifyAll(),线程可以通知其他等待的线程继续执行。这使得多个线程可以协调工作,确保它们在适当的时间点进行交互。

  • notify() :唤醒 Wait Set 中的一个随机线程。适用于只需要唤醒一个线程的情况。
  • notifyAll() :唤醒 Wait Set 中的所有线程。适用于需要唤醒多个线程的情况,或者不确定哪个线程应该被唤醒。
java 复制代码
synchronized (buffer) {
    buffer.notify();  // 唤醒一个等待的消费者线程
}

2.3 避免忙等待

如果没有 Wait Set,线程可能会使用忙等待(Busy Waiting)的方式不断检查条件是否满足。这种方式会浪费大量的CPU资源,降低系统的性能。Wait Set 通过让线程进入休眠状态,避免了不必要的CPU占用,提高了系统的效率。

java 复制代码
// 不推荐的忙等待方式
while (!condition) {
    // 一直循环检查条件,浪费CPU资源
}

// 推荐的使用 wait() 和 notify() 方式
synchronized (lock) {
    while (!condition) {
        lock.wait();  // 线程进入 Wait Set,等待条件满足
    }
    // 条件满足后继续执行
}

3. Wait Set 的工作原理

Wait Set 的工作原理如下:

  1. 线程进入 Wait Set
    • 当线程调用 wait() 时,它会释放当前持有的锁,并进入 Wait Set,暂时挂起自己。此时,线程不会参与对锁的竞争,也不会消耗CPU资源。
  2. 线程被唤醒
    • 当另一个线程调用 notify()notifyAll() 时,Monitor 会选择一个或多个等待的线程从 Wait Set 中移除,并将它们标记为可运行状态。被唤醒的线程会重新竞争锁,只有成功获取锁的线程才能继续执行。
  3. 线程重新获取锁
    • 被唤醒的线程在重新获取锁之前不会继续执行。这是因为 wait() 方法在返回时会自动重新获取锁,以确保线程在进入临界区时仍然持有锁。

4. 注意事项

  • 必须在 synchronized 块中调用 wait()notify()notifyAll()
    • 这些方法只能在已经获取了对象锁的情况下调用。如果在没有同步块的情况下调用这些方法,会抛出 IllegalMonitorStateException 异常。
  • wait() 应该放在 while 循环中
    • 由于 wait() 可能会被虚假唤醒(Spurious Wakeup),因此建议在 while 循环中使用 wait(),以确保线程只在条件真正满足时才继续执行。
java 复制代码
synchronized (lock) {
    while (!condition) {
        lock.wait();
    }
    // 条件满足后继续执行
}
  • notify()notifyAll() 的选择
    • 如果只需要唤醒一个线程,可以使用 notify()。如果不确定哪个线程应该被唤醒,或者需要唤醒多个线程,可以使用 notifyAll()。使用 notifyAll() 可以避免潜在的死锁问题,但可能会导致更多的线程竞争锁。

5. 示例:生产者-消费者问题

以下是一个简单的生产者-消费者问题的示例,展示了 Wait Set 的使用:

java 复制代码
public class ProducerConsumerExample {

    private static final Object lock = new Object();
    private static final Queue<Integer> buffer = new LinkedList<>();
    private static final int MAX_SIZE = 10;

    public static void main(String[] args) {
        Thread producer = new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                produce(i);
            }
        });

        Thread consumer = new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                consume();
            }
        });

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

    private static void produce(int value) {
        synchronized (lock) {
            while (buffer.size() == MAX_SIZE) {
                try {
                    System.out.println("Buffer is full, producer is waiting...");
                    lock.wait();  // 生产者进入 Wait Set,等待消费者消费
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            buffer.add(value);
            System.out.println("Produced: " + value);
            lock.notify();  // 唤醒一个等待的消费者
        }
    }

    private static void consume() {
        synchronized (lock) {
            while (buffer.isEmpty()) {
                try {
                    System.out.println("Buffer is empty, consumer is waiting...");
                    lock.wait();  // 消费者进入 Wait Set,等待生产者生产
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            int value = buffer.remove();
            System.out.println("Consumed: " + value);
            lock.notify();  // 唤醒一个等待的生产者
        }
    }
}

在这个示例中,生产者和消费者通过 wait()notify()Wait Set 中协作。生产者在缓冲区满时进入 Wait Set,等待消费者消费;消费者在缓冲区空时进入 Wait Set,等待生产者生产。通过这种方式,生产者和消费者可以高效地协同工作,避免忙等待和资源浪费。

6. 总结

  • Wait SetMonitor 中的一个重要组成部分,用于支持线程的条件等待和通知操作。
  • 它允许线程在满足某些条件之前暂时挂起自己,避免不必要的忙等待,从而提高系统的效率和响应性。
  • wait()notify()notifyAll()Wait Set 的核心操作,分别用于线程的等待、单个线程的唤醒和所有线程的唤醒。
  • 使用 Wait Set 时需要注意,wait() 必须在 synchronized 块中调用,并且通常应放在 while 循环中,以防止虚假唤醒。

理解 Wait Set 的工作原理和使用场景,可以帮助你更好地设计和实现高效的多线程程序,特别是在需要线程协作和条件等待的场景中。

相关推荐
真的想不出名儿14 分钟前
苍穹外卖-day07(Spring Cache & 购物车业务逻辑)
java·后端
Nijika...38 分钟前
libilibi项目总结(17)Elasticsearch 的使用
java·后端·spring·elasticsearch
shine_du1 小时前
架构师之路--springboot核心类SpringApplication方法run的源码启动流程
java·spring boot·后端
waicsdn_haha2 小时前
MySql-9.1.0安装详细教程(保姆级)
java·数据库·后端·mysql·php·性能测试·数据库开发
只会写bug的靓仔2 小时前
无管理员权限 LCU auth-token、port 获取(全网首发 go)
开发语言·后端·golang
我们的五年2 小时前
【Linux课程学习】:第二十一弹---深入理解信号(中断,信号,kill,abort,raise,larm函数)
linux·服务器·后端·深度学习·ubuntu·机器学习
愿时间能学会宽恕3 小时前
SpringBoot后端开发常用工具详细介绍——Minio资源管理器
java·spring boot·后端
一只淡水鱼663 小时前
【Spring】Controller层常用注解的介绍和使用
java·后端·spring
北辰浮光3 小时前
[maven]使用spring
java·后端·spring
凡人的AI工具箱3 小时前
每天40分玩转Django:Django认证系统
开发语言·数据库·后端·python·django