java并发编程中Monitor里的waitSet和EntryList都是做什么的

简介: 在Java并发编程中,Monitor内部包含两个重要队列:等待集(Wait Set)和入口列表(Entry List)。Wait Set用于线程的条件等待和协作,线程调用`wait()`后进入此集合,通过`notify()`或`notifyAll()`唤醒。Entry List则管理锁的竞争,未能获取锁的线程在此排队,等待锁释放后重新竞争。理解两者区别有助于设计高效的多线程程序。- **Wait Set**:线程调用`wait()`后进入,等待条件满足被唤醒,需重新竞争锁。- **Entry List**:多个线程竞争锁时,未获锁的线程在此排队,等待锁释放后获取锁继续执行。

背景

在并发编程学习中,学习的时候认识到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资源。
  1. 线程被唤醒
  • 当另一个线程调用 notify()notifyAll() 时,Monitor 会选择一个或多个等待的线程从 Wait Set 中移除,并将它们标记为可运行状态。被唤醒的线程会重新竞争锁,只有成功获取锁的线程才能继续执行。
  1. 线程重新获取锁
  • 被唤醒的线程在重新获取锁之前不会继续执行。这是因为 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 中排队,等待锁的释放。
  1. 线程获取锁
  • 当持有锁的线程退出 synchronized 块或方法时,它会释放锁。此时,Monitor 会从 Entry List 中选择一个线程,允许它获取锁并继续执行。选择的顺序通常是FIFO(先进先出),但JVM的具体实现可能会有所不同。
  1. 线程竞争锁
  • 如果有多个线程在 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 的区别和作用,可以帮助你更好地设计和实现高效的多线程程序,特别是在需要线程协作和锁管理的场景中。


转载来源:https://juejin.cn/post/7449452296603271204

相关文章
|
3月前
|
IDE Java 编译器
java编程最基础学习
Java入门需掌握:环境搭建、基础语法、面向对象、数组集合与异常处理。通过实践编写简单程序,逐步深入学习,打牢编程基础。
241 1
|
3月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
187 6
|
6月前
|
Java API 微服务
为什么虚拟线程将改变Java并发编程?
为什么虚拟线程将改变Java并发编程?
324 83
|
4月前
|
SQL Java 数据库
2025 年 Java 从零基础小白到编程高手的详细学习路线攻略
2025年Java学习路线涵盖基础语法、面向对象、数据库、JavaWeb、Spring全家桶、分布式、云原生与高并发技术,结合实战项目与源码分析,助力零基础学员系统掌握Java开发技能,从入门到精通,全面提升竞争力,顺利进阶编程高手。
787 1
|
3月前
|
安全 前端开发 Java
从反射到方法句柄:深入探索Java动态编程的终极解决方案
从反射到方法句柄,Java 动态编程不断演进。方法句柄以强类型、低开销、易优化的特性,解决反射性能差、类型弱、安全性低等问题,结合 `invokedynamic` 成为支撑 Lambda 与动态语言的终极方案。
179 0
|
5月前
|
安全 Java 数据库连接
2025 年最新 Java 学习路线图含实操指南助你高效入门 Java 编程掌握核心技能
2025年最新Java学习路线图,涵盖基础环境搭建、核心特性(如密封类、虚拟线程)、模块化开发、响应式编程、主流框架(Spring Boot 3、Spring Security 6)、数据库操作(JPA + Hibernate 6)及微服务实战,助你掌握企业级开发技能。
742 3
|
4月前
|
Java 开发者
Java并发编程:CountDownLatch实战解析
Java并发编程:CountDownLatch实战解析
481 100
|
4月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
301 16
|
4月前
|
NoSQL Java 关系型数据库
超全 Java 学习路线,帮你系统掌握编程的超详细 Java 学习路线
本文为超全Java学习路线,涵盖基础语法、面向对象编程、数据结构与算法、多线程、JVM原理、主流框架(如Spring Boot)、数据库(MySQL、Redis)及项目实战等内容,助力从零基础到企业级开发高手的进阶之路。
369 1