【多线程】轻松彻底搞懂ReentrantLock的底层实现原理

前言

上篇博客讲解了Sychronized的底层实现原理,它是基于jdk实现的,现在此篇博客讲解基于AQS 框架实现的另一个锁ReentrantLock

1、名词解释

为什么叫CLH队列

CLH 全称是三个人创造的,分别为Craig、Landin、Hagersten,所以以这个三个名字的首字母命名,原始的CLH锁是一种单向链表结构的自旋锁队列
每个节点仅包含:

前驱指针(myPred):指向队列中前一个节点
状态变量(locked):表示当前线程是否需要获取锁(true=需要自旋等待,false=可获取锁)
线程信息:封装当前等待线程

但是在ReentrantLock里它对原来的CLH队列进行了升级,Java 并发包中的 AQS 框架采用 CLH 锁的变体(虚拟双向队列(FIFO队列)),通过封装线程为 Node 对象实现锁分配。

2、ReentrantLock的实现

2.1 Node的数据结构

在AQS框架中,是使用队列的方式来实现同步管理的,队列中每个节点是Node节点,看下Node的数据结构,

        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;
        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        Node nextWaiter;

源码如上,其实核心的结构就是

thread:一个等待获取同步状态的线程

prev:指向上一个节点的引用

next:指向下一个节点的引用

waitStatus:等待的状态:
在这里插入图片描述
1.CANCELLED:值尾1,表示在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消等待,接入CANCELLED状态后将不会再变化
2.SIGNAL:值尾-1,表示后续节点的线程处于等待状态,如果当前节点的线程释放了同步状态或者被取消便会通知后续节点,使后续节点的线程得以运行
3.CONDITION,值为-2 表示节点再条件队列中,节点等待线程再Condition上,当其它线程队Condition调用了signal()后,该节点将会从条件队列中转移到同步队列中
4.PROPAGATE:值为-3,表示下一次共享式同步状态获取将会无条件传播下去

nextWaiter:表示条件队列中的后续节点,如果当前节点是共享的,那么这个字段将是一个SHARED变量static final Node SHARED = new Node();,也就是说节点类型(独占和共享)和条件队列中的后续节点共用同一个字段EXCLUSIVE:独占模式节点

AQS实现原理

上边介绍了AQS 中的Node节点,现在说下AQS实现的原理

核心元素
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
   
   
	// Node节点
    static final class Node {
   
   ...}
    // 指向头节点
    private transient volatile Node head;
    // 指向尾节点
    private transient volatile Node tail;
    // 表示资源的可用状态
    private volatile int state;
}
AQS 中的同步等待队列结构

AQS中 维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。
这里volatile能够保证多线程下的可见性,当state=1则代表当前对象锁已经被占有,其他线程来加锁时则会失败,加锁失败的线程会被放入一个FIFO的等待队列中,比列会被LockSupport.park()操作挂起,等待其他获取锁的线程释放锁才能够被唤醒。另外state的操作都是通过CAS来保证其并发修改的安全性。锁的可重入性state也会有所体现,当state等于0说明没有其它线程获取锁,可以被获取,获取后会设为1并将exclusiveOwnerThread设置为获取到锁的线程,如果不是等于0则判断是否是exclusiveOwnerThread的线程,是的话则获取锁,state+1,否则进入等待队列进行排队。需要注意的是head 指向的是一个虚拟节点,该节点不关联任何线程(thread=null),仅作为队列的起始标识。这种设计简化了队列操作,避免在无竞争时频繁创建和销毁节点
在这里插入图片描述

源码分析
1、ReentrantLock下的lock方法
   public ReentrantLock() {
   
   
        sync = new NonfairSync
### Java ReentrantLock 底层实现机制 #### 锁的核心概念 `ReentrantLock` 是基于 `AbstractQueuedSynchronizer (AQS)` 实现的一种可重入互斥锁。它支持公平和非公平两种模式,并且允许线程多次获取同一把锁而不会发生死锁[^3]。 #### AQS 的核心作用 AQS 提供了一个框架用于构建依赖于 FIFO 等待队列的阻塞锁和其他同步器。`ReentrantLock` 利用了 AQS 的独占模式,通过维护一个 `volatile int state` 变量来记录锁的状态。该变量表示当前锁被持有的次数[^4]。 #### 加锁过程 (`lock()` 方法) 当调用 `lock()` 方法时,`ReentrantLock` 会尝试获取锁。以下是其主要逻辑: 1. **状态检查** 调用 `nonfairTryAcquire(int acquires)` 方法检查锁是否已被其他线程持有。如果没有被占用,则尝试使用 CAS 操作设置 `state` 并标记当前线程为锁拥有者[^5]。 2. **可重入性处理** 如果锁已经被当前线程持有,则增加 `state` 计数以反映锁的嵌套级别。每次调用 `unlock()` 方法都会减少计数值,只有当计数归零时才会真正释放锁。 3. **排队等待** 若锁被其他线程持有且无法立即获取,则当前线程会被加入到 AQS 维护的 CLH 队列中并进入阻塞状态,直到轮到自己再次尝试获取锁[^1]。 #### 解锁过程 (`unlock()` 方法) 解锁操作相对简单,主要是减少 `state` 计数并唤醒下一个等待线程。具体流程如下: 1. 减少 `state` 值; 2. 当前线程确认不再持有锁后,通知后续节点重新竞争资源; 3. 如果有多个线程处于等待状态,按照 FIFO 或指定策略依次唤醒它们。 #### 公平与非公平锁的区别 - **非公平锁**:不考虑线程到达顺序,在任何情况下都可能直接尝试抢占锁,即使已有线程正在等待。这种方式提高了吞吐量但可能导致某些线程长期得不到执行机会(即饥饿现象)。 - **公平锁**:严格按照请求先后次序分配锁给线程,虽然更公正但也带来了额外开销,降低了整体性能[^2]。 ```java public class TestReentrantLock { private final ReentrantLock lock = new ReentrantLock(); public void testMethod() { lock.lock(); // 获取锁 try { System.out.println(Thread.currentThread().getName() + " acquired the lock."); } finally { lock.unlock(); // 释放锁 } } public static void main(String[] args) throws InterruptedException { TestReentrantLock test = new TestReentrantLock(); Thread t1 = new Thread(test::testMethod, "Thread-1"); Thread t2 = new Thread(test::testMethod, "Thread-2"); t1.start(); t2.start(); t1.join(); t2.join(); } } ``` 上述代码展示了如何创建以及管理基本的 `ReentrantLock` 实例,并演示了多线程环境下的锁定行为。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值