在Java的synchronized
同步机制中,Monitor对象的EntryList和WaitSet是两个关键队列,它们分别管理不同状态的线程。下面我将详细解释它们的工作原理,并提供代码示例说明。
EntryList(锁竞争队列)
作用机制
EntryList保存了所有等待获取锁的线程。当锁被某个线程持有时,其他尝试获取该锁的线程会被放入EntryList中,处于BLOCKED状态。
工作流程
- 线程尝试获取锁时发现锁已被持有
- 线程被放入EntryList等待
- 当持有锁的线程释放锁时,JVM会从EntryList中选择一个线程唤醒
- 被唤醒的线程尝试获取锁
代码示例
public class EntryListDemo {
private static final Object lock = new Object();
public static void main(String[] args) {
// 线程1先获取锁
new Thread(() -> {
synchronized (lock) {
System.out.println("线程1获取锁");
try {
Thread.sleep(3000); // 模拟长时间操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1释放锁");
}
}).start();
// 线程2稍后尝试获取锁
new Thread(() -> {
try {
Thread.sleep(100); // 确保线程1先获取锁
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) { // 这里会被放入EntryList等待
System.out.println("线程2获取锁");
}
}).start();
}
}
输出结果:
线程1获取锁
(等待约3秒)
线程1释放锁
线程2获取锁
WaitSet(等待队列)
作用机制
WaitSet保存了调用了wait()
方法的线程。这些线程已经获得了锁,但主动释放锁并等待被唤醒。
工作流程
- 线程获得锁后调用
wait()
方法 - 线程释放锁并进入WaitSet,处于WAITING状态
- 其他线程调用
notify()
/notifyAll()
时,线程从WaitSet转移到EntryList - 当再次获得锁时,从
wait()
调用处继续执行
代码示例
public class WaitSetDemo {
private static final Object lock = new Object();
private static volatile boolean condition = false;
public static void main(String[] args) throws InterruptedException {
// 等待线程
Thread waitingThread = new Thread(() -> {
synchronized (lock) {
System.out.println("等待线程获得锁");
while (!condition) {
try {
System.out.println("等待线程调用wait()");
lock.wait(); // 释放锁并进入WaitSet
System.out.println("等待线程被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("等待线程完成工作");
}
});
// 通知线程
Thread notifyingThread = new Thread(() -> {
synchronized (lock) {
System.out.println("通知线程获得锁");
condition = true;
lock.notify(); // 唤醒WaitSet中的一个线程
System.out.println("通知线程已发送通知");
}
});
waitingThread.start();
Thread.sleep(500); // 确保等待线程先执行
notifyingThread.start();
}
}
输出结果:
等待线程获得锁
等待线程调用wait()
通知线程获得锁
通知线程已发送通知
等待线程被唤醒
等待线程完成工作
EntryList和WaitSet的区别
特性 | EntryList | WaitSet |
---|---|---|
线程状态 | BLOCKED | WAITING |
进入方式 | 竞争锁失败自动进入 | 主动调用wait()进入 |
唤醒方式 | 锁释放时自动唤醒 | 必须通过notify()/notifyAll() |
锁的状态 | 从未获得过锁 | 曾获得锁但主动释放 |
转移目标 | 获得锁后进入运行状态 | 被唤醒后进入EntryList |
完整生命周期示例
public class MonitorLifecycleDemo {
private static final Object lock = new Object();
public static void main(String[] args) {
// 线程1:演示wait()和notify()
new Thread(() -> {
synchronized (lock) {
System.out.println("线程1获得锁");
try {
System.out.println("线程1调用wait()进入WaitSet");
lock.wait(); // 进入WaitSet
System.out.println("线程1被唤醒,重新获得锁");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1释放锁");
}
}).start();
// 线程2:竞争锁进入EntryList
new Thread(() -> {
try {
Thread.sleep(100); // 确保线程1先获得锁
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
System.out.println("线程2从EntryList中被选中获得锁");
System.out.println("线程2调用notify()唤醒WaitSet中的线程");
lock.notify(); // 唤醒线程1,线程1进入EntryList
try {
Thread.sleep(1000); // 保持锁一段时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2释放锁");
}
}).start();
// 线程3:展示EntryList中的竞争
new Thread(() -> {
try {
Thread.sleep(200); // 确保线程2已获得锁
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
System.out.println("线程3获得锁");
}
}).start();
}
}
输出结果:
线程1获得锁
线程1调用wait()进入WaitSet
线程2从EntryList中被选中获得锁
线程2调用notify()唤醒WaitSet中的线程
(等待约1秒)
线程2释放锁
线程1被唤醒,重新获得锁
线程1释放锁
线程3获得锁
这个示例展示了:
- 线程1获得锁后主动wait()进入WaitSet
- 线程2从EntryList中获得锁并notify()唤醒线程1
- 线程2释放锁后,线程1从WaitSet转移到EntryList并重新获得锁
- 最后线程3获得锁