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

我们可以了解到它是一个可重入锁,下面我们就一起看一下它的底层实现~

构造函数

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

public ReentrantLock() { sync = new NonfairSync();}public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync();}

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

公平锁 & 非公平锁

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

这种锁的优点很明显,每个线程都能够获取资源,缺点也很明显,如果某个线程阻塞了,其它线程也会阻塞,然而cpu唤醒开销很大,之前也给大家讲过

  • 非公平锁 多个线程都去尝试获取锁,获取不到就进入等待队列,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这个方法,是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) { 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可重入锁体验在哪里。

郑重声明:本文内容及图片均整理自互联网,不代表本站立场,版权归原作者所有,如有侵权请联系管理员(admin#wlmqw.com)删除。
用户投稿
上一篇 2022年6月19日 15:13
下一篇 2022年6月19日 15:13

相关推荐

  • 分享4条发微商朋友圈的方法(微商朋友圈应该怎么发)

    对于微商朋友来说,朋友圈的重要性不言而喻了。 那么微商的朋友圈到底该怎么发呢? 为什么同样是经营一个朋友圈,有的微商看起来逼格满满,实际效果也不错;而有的却动都不动就被屏蔽甚至拉黑…

    2022年11月27日
  • 存储过程语法(sql server存储过程语法)

    今天小编给各位分享存储过程语法的知识,其中也会对sql server存储过程语法进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧! oracle存储过程基本语法…

    2022年11月26日
  • 《宝可梦朱紫》梦特性怎么获得?隐藏特性获取方法推荐

    宝可梦朱紫里有很多宝可梦都是拥有梦特性会变强的宝可梦,很多玩家不知道梦特性怎么获得,下面就给大家带来宝可梦朱紫隐藏特性获取方法推荐,感兴趣的小伙伴一起来看看吧,希望能帮助到大家。 …

    2022年11月25日
  • 《宝可梦朱紫》奇鲁莉安怎么进化?奇鲁莉安进化方法分享

    宝可梦朱紫中的奇鲁莉安要怎么进化呢?很多玩家都不知道,下面就给大家带来宝可梦朱紫奇鲁莉安进化方法分享,感兴趣的小伙伴一起来看看吧,希望能帮助到大家。 奇鲁莉安进化方法分享 奇鲁莉安…

    2022年11月25日
  • cpu性能天梯图2022 AMD CPU天梯图最新排行榜出炉

    用户在DIY自己的主机时选择CPU是非常关键的,CPU可以说是电脑的大脑,大家也都想追求好一点的CPU来使用,但型号太多了,大部分的用户都不知道目前哪一款CPU比较好用,快来看看详…

    2022年11月24日
  • 《宝可梦朱紫》暴飞龙怎么抓?暴飞龙获得方法

    宝可梦朱紫暴飞龙位置在哪?在游戏中,很多玩家还不清楚暴飞龙具体要怎么样获得,其实获得方法很简单,暴飞龙直接是没得抓的,需要玩家从宝贝龙进化得到,下面一起来看一下宝可梦朱紫暴飞龙获得…

    2022年11月23日
  • 《宝可梦朱紫》布土拨怎么进化?布土拨进化方法介绍

    宝可梦朱紫中,不同的宝可梦有不同的进化方法,其中布土拨的进化方法是比较特殊的。很多玩家不知道宝可梦朱紫布土拨怎么进化,下面就带来宝可梦朱紫布土拨进化方法介绍,一起来看看吧,希望能帮…

    2022年11月23日
  • 《宝可梦朱紫》薄荷怎么获得?薄荷获得方法

    宝可梦朱紫中薄荷有改变宝可梦的属性或性格等效果,很多玩家想知道宝可梦朱紫薄荷怎么获得,下面就带来宝可梦朱紫薄荷获得方法,感兴趣的小伙伴一起来看看吧,希望能帮助到大家。 薄荷获得方法…

    2022年11月23日
  • 《宝可梦朱紫》怎么交换精灵?交换精灵方法一览

    宝可梦朱紫中玩家可以和好友或者npc进行交换宝可梦获得自己没有的宝可梦,很多玩家想知道宝可梦朱紫怎么交换精灵,下面就带来宝可梦朱紫交换精灵方法一览,感兴趣的小伙伴不要错过,希望能帮…

    2022年11月23日
  • 《宝可梦朱紫》龙爪技能怎么获得?龙爪技能获取方法

    宝可梦朱紫龙爪技能怎么获得?在游戏中,很多玩家还不清楚龙爪技能应该怎么获取,其实获取方法有很多,下面一起来看一下宝可梦朱紫龙爪技能获取方法,希望可以帮助各位玩家顺利的进行游戏内容。…

    2022年11月23日

联系我们

联系邮箱:admin#wlmqw.com
工作时间:周一至周五,10:30-18:30,节假日休息