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 Set 和 Entry List 的区别

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

4. 示例:Wait Set 和 Entry List 的结合使用

以下是一个简单的生产者-消费者问题的示例,展示了 Wait Set 和 Entry 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 Set 和 Entry List 的区别和作用,可以帮助你更好地设计和实现高效的多线程程序,特别是在需要线程协作和锁管理的场景中。

waitSet详解

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

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

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

相关推荐
看浪的路人15 小时前
第7讲:实时告警与自动化响应
开发语言·后端·golang
Gopher_HBo15 小时前
zap WriteSyncer与Sink体系
后端
合尘猫15 小时前
Nginx stream 做 GitHub 443 SNI 透传:完整配置、多上游故障转移与三个坑
后端·程序员·开源
知守观15 小时前
百万级数据导出OOM:POI的坑与EasyExcel的流式写入实战(附内存对比)
java·后端
蜗牛互联网15 小时前
Computer Use公共预览的安全设计:应用、动作与数据三维门禁
java·人工智能·后端·安全·策略模式
YYYing.15 小时前
【设计模式系列 (八) 】组合模式
c++·后端·设计模式·组合模式·c/c++
IT_陈寒15 小时前
Redis集群切换主节点时,服务竟然全员掉线
前端·人工智能·后端
打工仔折腾 AI16 小时前
多台服务器日志分散难查?用Promtail+Loki+Grafana搭建集中检索平台
服务器·人工智能·后端·python·性能优化·django·grafana
打工仔折腾 AI16 小时前
DiPlay 实测:iPhone 绕过硬件盒子直连 BYD 车机的思路拆解
android·人工智能·后端·python·gradle·iphone·ai agent 实战
打工仔折腾 AI1 天前
工业场景下时序库与实时计算一体化架构选型实践对比
java·开发语言·后端·python·性能优化·架构·ai agent 实战