三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

ReentrantLock 公平锁 非公平锁底层实现原理

ReentrantLock 公平锁  非公平锁底层实现原理

目录

一、基础铺垫:ReentrantLock 的核心结构

二、非公平锁(NonfairSync)底层实现

1. 加锁核心流程

2. 非公平锁关键特性

三、公平锁(FairSync)底层实现

1. 加锁核心流程

2. 公平锁关键特性

四、释放锁逻辑(公平 / 非公平完全一致)

五、核心区别总结(底层实现)

总结


ReentrantLock是 Java 中基于 AQS(AbstractQueuedSynchronizer,抽象队列同步器)实现的可重入锁,公平锁和非公平锁的核心区别:线程获取锁的顺序是否严格遵循等待队列的 FIFO(先进先出)规则

先明确核心结论:

  • 非公平锁:新线程来抢锁时,先直接尝试插队抢占锁,抢不到再进入等待队列排队;
  • 公平锁绝对禁止插队,新线程来抢锁时,先检查队列里有没有其他线程在等待,有就直接排队,不尝试抢占。

一、基础铺垫:ReentrantLock 的核心结构

  1. ReentrantLock内部定义了Sync抽象类(继承 AQS),公平锁FairSync、非公平锁NonfairSync都继承自 Sync;
  2. AQS 核心组件:
    • state:int 变量,0=无锁>0=持有锁(重入次数)
    • exclusiveOwnerThread:记录当前持有锁的线程;
    • CLH 等待队列:双向链表,存储抢不到锁的等待线程。
  3. 可重入特性:同一个线程多次获取锁时,state累加,释放锁时递减,直到state=0才真正释放锁。

二、非公平锁(NonfairSync)底层实现

核心设计:先插队,失败再排队,追求更高吞吐量(默认锁)。

1. 加锁核心流程

// 非公平锁lock()方法 final void lock() { // 第一步:直接插队!CAS尝试将state从0改为1(抢占锁) if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); // 抢占成功,设置持有线程 else acquire(1); // 抢占失败,走标准AQS获取锁逻辑 } // AQS的acquire方法(非公平/公平锁通用骨架) public final void acquire(int arg) { // 1. 再次尝试获取锁 2. 获取失败则入队等待 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } // 非公平锁的tryAcquire(重写AQS方法) protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } // 非公平锁核心获取逻辑 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 锁空闲 // 再次插队!CAS抢锁(不检查队列) if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 可重入:当前线程已持有锁,state+1 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; setState(nextc); return true; } return false; // 抢锁失败 }

2. 非公平锁关键特性

  1. 两次插队机会
    • 第一次:lock()直接 CAS 抢锁;
    • 第二次:tryAcquire()发现锁空闲,再次 CAS 抢锁;
  2. 不检查等待队列:只要锁空闲,新线程可以直接抢走,不管队列里有没有等待很久的线程;
  3. 优点:减少线程上下文切换,吞吐量高;缺点:可能导致队列线程 "饥饿"(长时间抢不到锁)。

三、公平锁(FairSync)底层实现

核心设计:不插队,先检查队列,排队获取锁,保证严格的先来先服务。

1. 加锁核心流程

// 公平锁lock()方法(直接走acquire,没有插队CAS) final void lock() { acquire(1); } // 公平锁的tryAcquire(重写AQS方法) protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 核心区别:先检查队列!hasQueuedPredecessors()=队列有等待线程 if (!hasQueuedPredecessors() && // 队列空,才允许抢锁 compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 可重入逻辑和非公平锁完全一致 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; setState(nextc); return true; } return false; }

2. 公平锁关键特性

  1. 禁止任何插队lock()直接走标准流程,没有 CAS 抢占
  2. 强制检查队列hasQueuedPredecessors()是核心判断:
    • 如果队列里有其他线程在等待 → 新线程直接入队,不抢锁;
    • 只有队列为空 / 自己是队首线程 → 才允许抢锁;
  3. 优点:无线程饥饿,严格公平;缺点:频繁上下文切换,吞吐量低于非公平锁。

四、释放锁逻辑(公平 / 非公平完全一致)

释放锁没有公平性区别,核心都是:

  1. state递减;
  2. state=0时,释放持有线程,唤醒队列中的下一个等待线程。
public void unlock() { sync.release(1); } public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); // 唤醒队列下一个线程 return true; } return false; } // 释放锁:state-1,为0时释放锁 protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }

五、核心区别总结(底层实现)

对比维度非公平锁(NonfairSync)公平锁(FairSync)
抢锁入口lock()先 CAS 插队抢锁直接走acquire(),不插队
锁空闲时不检查队列,直接 CAS 抢锁必须先检查hasQueuedPredecessors()
等待队列新线程可能跳过队列严格遵守 FIFO 队列顺序
线程饥饿可能出现不会出现
吞吐量
底层差异无队列检查多了hasQueuedPredecessors()判断

总结

  1. 底层基石:公平 / 非公平锁都基于AQS实现,核心差异在tryAcquire()抢锁逻辑;
  2. 非公平锁插队优先,两次 CAS 抢锁,不检查等待队列,吞吐量更高;
  3. 公平锁排队优先,强制检查队列,绝对禁止插队,无饥饿但性能更低;
  4. 通用特性:可重入、锁释放逻辑、CLH 等待队列、AQS 的state状态管理完全一致。
← 返回列表