面试官:有了解过ReentrantLock的底层实现吗?说说看

IT科技类资讯2025-11-03 00:59:148598

源码剖析

​​上节​​带大家学习了它的面试基本使用,我们可以了解到它是解过一个可重入锁,下面我们就一起看一下它的底层实现~

构造函数

我们在使用的时候,都是的底层先new它,所以我们先看下它的实现说说构造函数,它主要有两个:

public ReentrantLock() {

sync = new NonfairSync();

}

public ReentrantLock(boolean fair) {

sync = fair ? new FairSync() : new NonfairSync();

}

从字面上看,它们之间的面试不同点在于fair,翻译过来就是公平的意思,大体可以猜到它是用来构建公平锁和非公平锁,在继续往下看源码之前,先给大家科普一下这两种锁。解过

公平锁 & 非公平锁

公平锁多个线程按照申请锁的的底层顺序去获得锁,线程会直接进入队列去排队,实现说说永远都是面试队列的第一位才能得到锁。(例如银行办业务取号)。解过

这种锁的的底层优点很明显,每个线程都能够获取资源,实现说说缺点也很明显,面试如果某个线程阻塞了,解过其它线程也会阻塞,的底层然而cpu唤醒开销很大,b2b信息网之前也给大家讲过。

非公平锁多个线程都去尝试获取锁,获取不到就进入等待队列,cpu也不用去唤醒。

优缺点正好和上边相反,优点减少开销,缺点也很明显,可能会导致一直获取不到锁或长时间获取不到锁。

好,有了基本概念之后,我们继续往下看。

NonfairSync

首先,我们看下非公平锁,默认情况下,我们申请的都是非公平锁,也就是new ReentrantLock(),我们接着看源码。

static final class NonfairSync extends Sync {

private static final long serialVersionUID = 7316153563782823691L;

/

**

* Performs lock. Try immediate barge, backing up to normal

* acquire on failure.

*/

final void lock() {

if (compareAndSetState(0, 1))

setExclusiveOwnerThread(Thread.currentThread());

else

acquire(1);

}

protected final boolean tryAcquire(int acquires) {

return nonfairTryAcquire(acquires);

}

}

它继承了Sync,Sync是一个内容静态抽象类:

abstract static class Sync extends AbstractQueuedSynchronizer {...}

分为公平和非公平,使用AQS状态来表示持锁的次数,在构造函数初始化的时候都有sync = ...,我们接着看NonfairSync。在使用的时候,我们调用了lock.lock()方法,它是ReentrantLock的一个实例方法。

// 获取锁

public void lock() {

sync.lock();

}

实际上内部还是调了sync的内部方法,因为我们申请的是非公平锁,所以我们看NonfairSync下的lock实现:

final void lock() {

if (compareAndSetState(0, 1))

setExclusiveOwnerThread(Thread.currentThread());

else

acquire(1);

}

compareAndSetState这个方法,是WordPress模板AQS的内部方法,意思是如果当前状态值等于预期值,则自动将同步状态设置为给定的更新值。此操作具有volatile读写的内存语义。

protected final boolean compareAndSetState(int expect, int update) {

// See below for intrinsics setup to support this

return unsafe.compareAndSwapInt(this, stateOffset, expect, update);

}

可以看到执行lock方法,会通过AQS机制计数,setExclusiveOwnerThread设置线程独占访问权限,它是AbstractOwnableSynchronizer的一个内部方法,子类通过使用它来管理线程独占。

public abstract class AbstractQueuedSynchronizer

extends AbstractOwnableSynchronizer

implements java.io.Serializable {}

可以看到它是继承了AbstractOwnableSynchronizer。下面接着看,我们说如果实际值等于期望值会执行上边的方法,不期望的时候会执行acquire(1)。

public final void acquire(int arg) {

if (!tryAcquire(arg) &&

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

selfInterrupt();

}

这个方法以独占模式获取,忽略中断,它会尝试调用tryAcquire,成功会返回,不成功进入线程排队,可以重复阻塞和解除阻塞。看下AQS 内部的这个方法。

protected boolean tryAcquire(int arg) {

throw new UnsupportedOperationException();

}

我们可以看到实现肯定不在这,它的具体实现在NonfairSync。

protected final boolean tryAcquire(int acquires) {

return nonfairTryAcquire(acquires);

}

可以看到它调用了,nonfairTryAcquire方法,这个方法是不公平的tryLock,具体实现在Sync内部,这里我们要重点关注一下。亿华云

final boolean nonfairTryAcquire(int acquires) {

final Thread current = Thread.currentThread();

// 返回同步状态值,它是AQS内部的一个方法

// private volatile int state;

// protected final int getState() {

// return state;

//}

int c = getState();

if (c == 0) {

// 为0就比较一下,如果与期望值相同就设置为独占线程,说明锁已经拿到了

if (compareAndSetState(0, acquires)) {

setExclusiveOwnerThread(current);

return true;

}

}

// 否则 判断如果当前线程已经是被设置独占线程了

else if (current == getExclusiveOwnerThread()) {

// 设置当前线程状态值 + 1 并返回成功

int nextc = c + acquires;

if (nextc < 0) // overflow

throw new Error("Maximum lock count exceeded");

setState(nextc);

return true;

}

// 否则返回失败 没拿到锁

return false;

}

好,我们再回过头看下 acquire。

public final void acquire(int arg) {

// 如果当前线程没有获取到锁 并且 在队列中的线程尝试不断拿锁如果被打断了会返回true, 就会调用 selfInterrupt

if (!tryAcquire(arg) &&

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

selfInterrupt();

}

selfInterrupt很好理解,线程中断。

static void selfInterrupt() {

Thread.currentThread().interrupt();

}

其实我们关注的重点是这个方法acquireQueued,首先关注一下入参,它内部传入了一个addWaiter,最后它回NODE节点。

private Node addWaiter(Node mode) {

// mode 没啥好说的就是一个标记,用于标记独占模式 static final Node EXCLUSIVE = null;

Node node = new Node(Thread.currentThread(), mode);

// Try the fast path of enq; backup to full enq on failure

Node pred = tail;

if (pred != null) {

node.prev = pred;

if (compareAndSetTail(pred, node)) {

pred.next = node;

return node;

}

}

enq(node);

return node;

}

我们可以大体从猜到,Node是一个等待队列的节点类,是一个链表结构,之前我们讲FutureTask源码的时候也遇到过这种结构,它通常用于自旋锁,在这个地方,它是用于阻塞同步器。

+------+ prev +-----+ +-----+

head | | <---- | | <---- | | tail

+------+ +-----+ +-----+

好,下面我们关注一下 acquireQueued。

final boolean acquireQueued(final Node node, int arg) {

boolean failed = true;

try {

// 默认是 false

boolean interrupted = false;

// 进入阻塞循环遍历 线程队列

for (;;) {

// 返回前一个节点

final Node p = node.predecessor();

// 判断如果前一个节点是头部节点,并且拿到锁了,就会设置当前节点为头部节点

if (p == head && tryAcquire(arg)) {

setHead(node);

// 这里可以看到注释 help gc ,

p.next = null; // help GC

failed = false;

return interrupted;

}

// 检查并更新未能获取的节点的状态。如果线程应该阻塞,则返回 true 并且线程中断了

if (shouldParkAfterFailedAcquire(p, node) &&

parkAndCheckInterrupt())

interrupted = true;

}

} finally {

// 如果失败 取消正在尝试获取的节点

if (failed)

cancelAcquire(node);

}

}

从上面的源码来看,在体会一下上面讲的非公平锁的概念,是不是更好理解一些,然后就是释放锁unlock,这个方法我们可以看到是ReentrantLock下的一个实例方法,所以公平锁的释放锁也是调的这个方法,其实最终可以猜到调用的还是sync的方法。

public void unlock() {

sync.release(1);

}

Sync继承AQS,release是AQS的内部方法。

public final boolean release(int arg) {

// 尝试释放锁 tryRelease 在Sync内部

if (tryRelease(arg)) {

Node h = head;

// 如果节点存在 并且状态值不为0

if (h != null && h.waitStatus != 0)

// 唤醒下个节点

unparkSuccessor(h);

return true;

}

return false;

}private void unparkSuccessor(Node node) {

int ws = node.waitStatus;

if (ws < 0)

compareAndSetWaitStatus(node, ws, 0);

Node s = node.next;

if (s == null || s.waitStatus > 0) {

s = null;

for (Node t = tail; t != null && t != node; t = t.prev)

if (t.waitStatus <= 0)

s = t;

}

if (s != null)

// 可以看到调用了 LockSupport来唤醒

LockSupport.unpark(s.thread);

}

我们再看下tryRelease, 同样这个实现在Sync内。

protected final boolean tryRelease(int releases) {

// 同样释放锁的时候 依然使用 AQS计数

int c = getState() - releases;

// 判断当前线程是否是独占线程,不是抛出异常

if (Thread.currentThread() != getExclusiveOwnerThread())

throw new IllegalMonitorStateException();

boolean free = false;

// 如果是0 表示是释放成功

if (c == 0) {

free = true;

// 并且把独占线程设为null

setExclusiveOwnerThread(null);

}

// 更新状态值

setState(c);

return free;

}

FairSync

公平锁FairSync的区别在于,它的获取锁的实现在它的内部,Sync默认内部实现了非公平锁。

static final class FairSync extends Sync {

private static final long serialVersionUID = -3000897897090466540L;

// 这个方法最终调用 tryAcquire

final void lock() {

acquire(1);

}

// 公平锁的实现

protected final boolean tryAcquire(int acquires) {

final Thread current = Thread.currentThread();

int c = getState();

// 这边和非公平锁的实现有些相似 同样判断状态

if (c == 0) {

// 判断排队队列是否存在, 不存在并且比较期望值

if (!hasQueuedPredecessors() &&

compareAndSetState(0, acquires)) {

// 设置独占线程 并返回成功

setExclusiveOwnerThread(current);

return true;

}

}

// 这边和上面类似

else if (current == getExclusiveOwnerThread()) {

int nextc = c + acquires;

if (nextc < 0)

throw new Error("Maximum lock count exceeded");

setState(nextc);

return true;

}

return false;

}

}

它的实现比较简单,通过实现可以发现,它按照申请锁的顺序来获取锁,排第一的先拿到锁,在结合上面的概念理解一下,就很好理解了。

释放锁unlock,上面我们已经讲过了。

结束语

本节内容可能有点多,主要是看源码,可以打断点自己调一下, 举一反三,通过源码去理解一下什么是公平锁和非公平锁, ReentrantLock可重入锁体验在哪里。

本文地址:http://www.bzve.cn/html/030f62299347.html
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

热门文章

全站热门

搭建个人服务器的费用标准(详细解析自己搭建服务器所需的费用和预算)

一下域名,看有没有显示出你所解析的IP,如果有,就说明解析是生效的;如果没有,就说明解析是不生效的。

2、根据用户基础选择访问提供程序。由于互联问题的存在,接入商的选择也非常重要,如果用户群主要在联通,尽量选择联通接入较好的接入商,如果用户群主要在电信,那么选择电信接入较好的接入商。如果用户组位于国家/地区,则选择更好的访问提供程序进行交互。

3.dns修改成功后,点击“域名解析”,按提示进行操作。解析格式一般如下:

将MOV格式视频转换为MP4的简单方法(以MOV格式的视频为例,教你一步步将视频转换为MP4格式)

二、如何选择合适的域名

并非一个好米任何人都会给你一个好的价格。那你该如何用以有的好米卖出最理想的价格呢?

第六:这个圈子里的域名确实是赚钱的一些大玩家,至于小米农,有多少赚钱?几乎没有,也就是说,轿子里只有一个人,而且大多数人都抬着轿子。

友情链接

滇ICP备2023006006号-39