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

相关文章
|
2月前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
1月前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界中,异常处理是代码健壮性的守护神。本文将带你从异常的基本概念出发,逐步深入到高级用法,探索如何优雅地处理程序中的错误和异常情况。通过实际案例,我们将一起学习如何编写更可靠、更易于维护的Java代码。准备好了吗?让我们一起踏上这段旅程,解锁Java异常处理的秘密!
|
15天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
15天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
98 2
|
2月前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
2月前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
1月前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
1月前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
51 3
|
1月前
|
开发框架 安全 Java
Java 反射机制:动态编程的强大利器
Java反射机制允许程序在运行时检查类、接口、字段和方法的信息,并能操作对象。它提供了一种动态编程的方式,使得代码更加灵活,能够适应未知的或变化的需求,是开发框架和库的重要工具。
52 3
|
2月前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界里,异常是程序运行中不可忽视的“惊喜”。它们可能突如其来,也可能悄无声息地潜伏。掌握异常处理的艺术,意味着你能够优雅地面对程序的不完美,并确保它即使在风雨飘摇中也能继续航行。本文将引导你理解Java异常的本质,探索捕获和处理这些异常的方法,并最终学会如何利用自定义异常为你的代码增添力量。