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

相关文章
|
4月前
|
存储 Java 调度
【多线程面试题 八】、说一说Java同步机制中的wait和notify
Java同步机制中的wait()、notify()、notifyAll()是Object类的方法,用于线程间的通信,其中wait()使当前线程释放锁并进入阻塞状态,notify()唤醒单个等待线程,notifyAll()唤醒所有等待线程。
|
7月前
|
Java 程序员 编译器
Monitor(管程)是什么意思?Java中Monitor(管程)的介绍
Monitor(管程)是什么意思?Java中Monitor(管程)的介绍
104 1
|
监控 前端开发 JavaScript
shin-monitor源码分析
shin-monitor源码分析
|
Linux 调度
《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)(三)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)(三)
|
安全 Java API
《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)(一)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)(一)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)(四)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)(四)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)(二)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)(二)
|
缓存 索引
《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile(三)
《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile
《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile(三)
|
存储 Java 编译器
《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile(二)
《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile
《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile(二)
《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile(四)
《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile
《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile(四)

热门文章

最新文章