【JAVA】synchronized 关键字的底层实现涉及得三个队列
synchronized 关键字的底层实现确实涉及三个主要的队列:cxq(Contention Queue)、EntryList 和 waitSet。这三个队列分别用于管理不同类型的等待线程。下面是每个队列的详细解释及其作用:
1. cxq(Contention Queue)
定义:cxq 是一个竞争队列,用于存储那些未能立即获得锁的线程。
行为:当一个线程试图获取一个已经被其他线程持有的锁时,该线程会被放入 cxq 中等待。cxq 是一个基于链表的数据结构,线程按照先进先出(FIFO)的原则排队。当锁被释放时,cxq 中的一个线程会被移到 EntryList 中,准备重新竞争锁。
2. EntryList
定义:EntryList 是一个等待队列,用于存储那些已经准备好竞争锁的线程。
行为:当 cxq 中的线程被移到 EntryList 中时,它们会尝试获取锁。
EntryList 也是一个基于链表的数据结构,线程按照先进先出(FIFO)的原则排队。当锁被释放时,EntryList 中的一个线程会被选中并获得锁。
3. waitSet
定义:waitSet 是一个条件等待队列,用于存储那些调用 wait 方法后被阻塞的线程。
行为:
当一个线程在临界区内调用 wait 方法时,它会释放锁并被放入 waitSet 中等待。
当其他线程调用 notify 方法时,waitSet 中的一个线程会被唤醒并重新竞争锁。
当其他线程调用 notifyAll 方法时,waitSet 中的所有线程都会被唤醒并重新竞争锁。
详细流程
获取锁:
线程 A 尝试进入由 synchronized 保护的代码块。
如果锁可用,线程 A 获得锁并进入临界区。
如果锁不可用,线程 A 被放入 cxq 中等待。
进入临界区:
线程 A 获得锁并进入临界区,执行受保护的代码。
调用 wait 方法:
线程 A 在临界区内调用 wait 方法。
线程 A 释放锁并被放入 waitSet 中等待。
其他线程(例如线程 B)可以竞争并获得锁。
释放锁:
当线程 B 完成临界区的执行或调用 notify/notifyAll 方法时,它会释放锁。释放锁后,cxq 中的一个线程(例如线程 C)会被移到 EntryList 中,准备重新竞争锁。
唤醒等待线程:
notify 方法:唤醒 waitSet 中的一个线程,使其重新竞争锁。
notifyAll 方法:唤醒 waitSet 中的所有线程,它们都会尝试获取锁。
释放锁后的具体行为
释放锁后:
优先从 cxq 中取出一个线程,将其移到 EntryList 中。
EntryList 中的线程会尝试获取锁。
如果 EntryList 为空,才会考虑 waitSet 中的线程。
public class SynchronizedExample {private final Object lock = new Object();public void methodA() {synchronized (lock) {System.out.println(Thread.currentThread().getName() + " entered methodA");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " exiting methodA");}}public void methodB() {synchronized (lock) {System.out.println(Thread.currentThread().getName() + " entered methodB");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " exiting methodB");}}public void methodC() {synchronized (lock) {System.out.println(Thread.currentThread().getName() + " entered methodC");try {Thread.sleep(1000);lock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " exiting methodC");}}public void methodD() {synchronized (lock) {System.out.println(Thread.currentThread().getName() + " entered methodD");try {Thread.sleep(1000);lock.notifyAll();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " exiting methodD");}}public static void main(String[] args) {SynchronizedExample example = new SynchronizedExample();Thread thread1 = new Thread(() -> example.methodA(), "Thread1");Thread thread2 = new Thread(() -> example.methodB(), "Thread2");Thread thread3 = new Thread(() -> example.methodC(), "Thread3");Thread thread4 = new Thread(() -> example.methodC(), "Thread4");Thread thread5 = new Thread(() -> example.methodD(), "Thread5");thread1.start();thread2.start();thread3.start();thread4.start();thread5.start();}
}
解释
获取锁:
Thread1 和 Thread2 同时尝试进入 methodA 和 methodB,这两个方法都使用同一个 lock 对象。
只有一个线程可以获得锁并进入临界区,另一个线程会被放入 cxq 中等待。
进入临界区:
获得锁的线程进入临界区,执行 System.out.println 和 Thread.sleep 语句。
调用 wait 方法:
Thread3 和 Thread4 在 methodC 中调用 wait 方法,释放锁并被放入 waitSet 中等待。其他线程(例如 Thread5)可以竞争并获得锁。
释放锁:
当 Thread5 在 methodD 中调用 notifyAll 方法时,它会释放锁。
释放锁后,cxq 中的一个线程(例如 Thread1 或 Thread2)会被移到 EntryList 中,准备重新竞争锁。
waitSet 中的所有线程(例如 Thread3 和 Thread4)也会被唤醒并重新竞争锁。
输出结果:
由于 synchronized 的互斥性,输出结果会显示多个线程依次进入和退出临界区。
总结
cxq:竞争队列,用于存储那些未能立即获得锁的线程。
EntryList:等待队列,用于存储那些已经准备好竞争锁的线程。
waitSet:条件等待队列,用于存储那些调用 wait 方法后被阻塞的线程。
通过这三个队列,synchronized 能够在高并发环境下有效地管理线程的竞争和等待,确保线程同步的公平性和效率