背景
在并发编程学习中,学习的时候认识到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()
方法来唤醒它。
工作原理:
- 线程进入
Wait Set
:
- 当线程调用
wait()
时,它会释放当前持有的锁,并进入Wait Set
,暂时挂起自己。此时,线程不会参与对锁的竞争,也不会消耗CPU资源。
- 线程被唤醒:
- 当另一个线程调用
notify()
或notifyAll()
时,Monitor
会选择一个或多个等待的线程从Wait Set
中移除,并将它们标记为可运行状态。被唤醒的线程会重新竞争锁,只有成功获取锁的线程才能继续执行。
- 线程重新获取锁:
- 被唤醒的线程在重新获取锁之前不会继续执行。这是因为
wait()
方法在返回时会自动重新获取锁,以确保线程在进入临界区时仍然持有锁。
使用场景:
- 条件等待:线程可以通过
wait()
方法进入Wait Set
,并在满足某个条件之前暂停执行。这通常用于生产者-消费者模式、读写锁等场景,其中线程需要等待某些资源或条件发生变化。 - 线程协作:通过
notify()
和notifyAll()
,线程可以通知其他等待的线程继续执行。这使得多个线程可以协调工作,确保它们在适当的时间点进行交互。
示例:
java
代码解读
复制代码
synchronized (lock) {
while (!condition) {
lock.wait(); // 线程进入 Wait Set,等待条件满足
}
// 条件满足后继续执行
}
2. 入口列表(Entry List)
定义:
- 入口列表(Entry List) 是一个包含所有试图获取锁但未能成功获取的线程的集合。当多个线程尝试进入同一个
synchronized
块或方法时,只有一个线程能够成功获取锁并进入临界区,其他线程会被阻塞并放入Entry List
,等待锁的释放。
工作原理:
- 线程进入
Entry List
:
- 当多个线程同时尝试获取同一个对象的锁时,只有一个线程能够成功获取锁并进入临界区,其他线程会被阻塞并放入
Entry List
。这些线程会在Entry List
中排队,等待锁的释放。
- 线程获取锁:
- 当持有锁的线程退出
synchronized
块或方法时,它会释放锁。此时,Monitor
会从Entry List
中选择一个线程,允许它获取锁并继续执行。选择的顺序通常是FIFO(先进先出),但JVM的具体实现可能会有所不同。
- 线程竞争锁:
- 如果有多个线程在
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
的区别和作用,可以帮助你更好地设计和实现高效的多线程程序,特别是在需要线程协作和锁管理的场景中。