【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 能够在高并发环境下有效地管理线程的竞争和等待,确保线程同步的公平性和效率

### Java 中 `synchronized` 关键字与 `ReentrantLock` 的底层实现原理 #### 一、`synchronized` 的底层实现 `synchronized` 是一种内置锁机制,由 JVM 提供支持。它的核心依赖于 **Monitor(监视器)** 和 **Object Header(对象头)**。 - 每个 Java 对象都有一个关联的 Monitor 对象[^3]。当线程试图进入被 `synchronized` 修饰的方法或代码块时,它实际上是在尝试获取该对象的 Monitor 锁。 - 如果多个线程同时请求同一个对象的 Monitor 锁,则只有其中一个线程能够成功获得锁并继续执行,其他线程则会被阻塞,直到当前持有锁的线程释放锁为止[^2]。 在 JVM 实现层面,`synchronized` 的具体过程如下: 1. 当线程首次尝试访问某个同步区域时,JVM 会在对象头中标记锁的状态。 2. 若锁未被占用,则直接标记为已锁定状态,并允许线程进入同步区。 3. 若锁已被占用,则当前线程将加入到等待队列中,直至锁可用。 4. 同步结束后,线程会自动释放锁,使下一个排队的线程有机会获取锁。 此外,`synchronized` 还利用了 CAS(Compare And Swap)、自旋锁以及轻量级锁升级等优化技术来提升性能[^4]。 --- #### 二、`ReentrantLock` 的底层实现 `ReentrantLock` 是基于 AQS(AbstractQueuedSynchronizer)框架实现的一种可重入锁。AQS 使用了一个 FIFO 队列结构管理线程间的竞争关系,并通过原子操作维护共享资源的状态。 以下是 `ReentrantLock` 的主要工作流程: 1. 调用 `lock()` 方法时,线程首先尝试以独占方式获取锁。如果此时没有其他线程占有锁,则当前线程可以直接获取锁并设置其拥有者。 2. 如果锁已经被另一个线程占据,则调用方线程会被挂起,并放入 AQS 维护的 CLH(Craig, Landin, and Hagersten Locks)队列中等待。 3. 解锁时,持有锁的线程调用 `unlock()` 方法,这会导致唤醒队列中最前面的一个等待线程去重新争夺锁[^1]。 值得注意的是,`ReentrantLock` 支持公平性和非公平性两种模式的选择,默认是非公平锁。这种灵活性使得开发者可以根据实际需求调整程序行为。 --- #### 三、两者的对比分析 | 特性 | `synchronized` | `ReentrantLock` | |-------------------------|----------------------------------------|---------------------------------------| | 是否需要显式解锁 | 不需要 | 需要 | | 性能优化 | 自动完成 | 更灵活可控 | | 条件变量支持 | 单一条件 | 多个独立的 Condition | | 可中断特性 | 不支持 | 支持 | 尽管两者都能满足基本的并发控制需求,但在某些场景下,比如需要更复杂的锁功能或者更高的定制化程度时,推荐使用 `ReentrantLock`。 --- ```java // ReentrantLock 示例代码 import java.util.concurrent.locks.ReentrantLock; public class Counter { private int count = 0; private final ReentrantLock lock = new ReentrantLock(); public void increment() { lock.lock(); // 加锁 try { count++; } finally { lock.unlock(); // 确保最终总是解锁 } } public int getCount() { return count; } } ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值