ReentrantLock 源码分析   以公平锁源码解析为例:

1:数据结构:

维护Sync 对象的引用:   private final Sync sync;

Sync对象继承 AQS,  Sync  分为两个类:处理公平锁锁和非公平锁:

FairSync   NonfairSync

具体的类图如下:

2:接下来重点分析AQS这个类:AbstractQueuedSynchronizer:

AQS中的成员变量:

private transient volatile Node head;   //AQS维护队列的头结点

private transient volatile Node tail;     // AQS维护队列的尾结点

private volatile int state;                            // AQS 锁的状态  数量标识锁被获取的次数

下面看看NODE 结点的成员变量:

volatile int waitStatus;   //等待状态

volatile Node prev;      //前继节点

volatile Node next;      //后继节点

volatile Thread thread;   //线程对象

Node nextWaiter;       //下一个等待节点

从NODE的数据结构可以看出来,AQS里面维护的队列的数据结构是双链表的形式;

 
   

3:接下来分析 ReentrantLock  的构造方法:

ReentrantLock lock = new ReentrantLock(true);   //传入true,说明是构造公平锁

具体的构造方法如下,返回FairSync 对象:

public ReentrantLock(boolean fair) {

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

}

4:lock方法的分析:因为是公平锁,所以调用 FairSync 下的lock方法:

final void lock() {

acquire(1);

}

Acquire 的方法如下:

public final void acquire(int arg) {    // arg=1

if (!tryAcquire(arg) &&

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

selfInterrupt();

}

接下来分两种场景来分析tryAcquire(arg)  1:同一线程第一次或N次获取锁(其他线程没有获取到锁)   2:其他线程已经获取到锁,当前线程尝试去获取锁;

//场景 1:

protected final boolean tryAcquire(int acquires) {   // acquires=1

final Thread current = Thread.currentThread();   //当前线程:main-thread

int c = getState();    //如果为第一次获取锁c=0  如果main线程已经获取过锁,则c为加锁的次数

if (c == 0) {     //当前线程第一次获取锁

if (!hasQueuedPredecessors() &&  // hasQueuedPredecessor的分析如下单独分析:

compareAndSetState(0, acquires)) {  //cas原子操作 state=1

setExclusiveOwnerThread(current);  //独占线程设置为当前线程

return true;  //返回true表明加锁成功

}

}

else if (current == getExclusiveOwnerThread()) {  // c=n 的情况下

int nextc = c + acquires;    // nextc=n+1

if (nextc < 0)

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

setState(nextc);    //设置 state=n+1

return true;  //获取到锁,返回true

}

return false;

}

说明:hasQueuedPredecessors主要是判断当前线程所在的节点是不是CLH队列的首个位置,这个判断的目的是公平锁的公平获取锁的机制

hasQueuedPredecessors的源码如下:以该线程是第一次获取锁为例分析:

public final boolean hasQueuedPredecessors() {

Node t = tail;      // tail=null

Node h = head;    // head=null

Node s;

return h != t &&    //返回false

((s = h.next) == null || s.thread != Thread.currentThread());

}

//场景2 分析:有其他线程未释放锁(main-thread 持有锁,thread-1尝试去获取锁)

protected final boolean tryAcquire(int acquires) {   // acquires=1

final Thread current = Thread.currentThread();   //当前线程:thread-1

int c = getState();    // 由于其他持有锁 state 至少为1

if (c == 0) {

if(!hasQueuedPredecessors()&&  compareAndSetState(0, acquires)) {

setExclusiveOwnerThread(current);

return true;

}

}

else if (current == getExclusiveOwnerThread()) {  // current=thread1  // getExclusiveOwnerThread()=main

int nextc = c + acquires;

if (nextc < 0)

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

setState(nextc);

return true;

}

return false;   //此时返回false表明尝试获取锁失败

}

5:上面的场景1 获取到锁后返回true,则lock 方法执行结束。下面分析场景2:

public final void acquire(int arg) {

if (!tryAcquire(arg) &&     //尝试获取锁失败,返回false

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

selfInterrupt();

}

!tryAcquire(arg)  返回为true;接下来进入

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  这个逻辑;

这个方法有两层处理:

1:addWaiter(Node.EXCLUSIVE)   将当前线程调价到CLH队列中

2:acquireQueued();逐步执行CLH队列中的线程,如果当前线程获取到锁则返回,否则,当前线程进行休眠,直到唤醒并重新获取到锁才返回;

以下分析这两个方法:场景:main线程获取到锁但未释放,这是线程 thread-1去获取锁:(假设此时没有其他线程在CLH队列中,即CLH队列为null)

addWaiter(Node.EXCLUSIVE)方法:入参 Node.EXCLUSIVE 为null;

private Node addWaiter(Node mode) {   //mode=null  EXCLUSIVE 标识节点为独占锁模型

Node node = new Node(Thread.currentThread(), mode); //创建新节点:新节点中线程为当前线程,节点模型为独占锁:thread= thread-1   nextWaiter= mode

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

Node pred = tail;   // 此时tail=null

if (pred != null) {

node.prev = pred;

if (compareAndSetTail(pred, node)) {

pred.next = node;

return node;

}

}

enq(node);  //进入enq方法

return node;

}

Enq() 方法如下:

private Node enq(final Node node) {   //入参为上一步新建的节点

for (;;) {

Node t = tail;    // 第一次遍历逻辑 tail=null

if (t == null) { // Must initialize

if (compareAndSetHead(new Node()))  //CAS 创建表头 Head

tail = head;

} else {

node.prev = t;  //第二次遍历逻辑:node为上面新建的节点:thread= thread-1   nextWaiter= mode, node.prev指向表头 t 为表头

if (compareAndSetTail(t, node)) {  //CAS设置队列尾节点为当前node

t.next = node;   // 表头后继节点指向当前节点

return t;

}

}

}

}

该场景经过上面的处理之后 CLH队列的数据结构如下:

第一次遍历:

Head,tail节点

第二次遍历:

Head               当前线程node:设置为tail

 
   

接下来分析

acquireQueued这个方法

final boolean acquireQueued(final Node node, int arg) {  //node为当前线程节点 arg=1

boolean failed = true;

try {

boolean interrupted = false; //当前线程在休眠时,有没有被中断过

for (;;) {

final Node p = node.predecessor(); //获取前继节点,这里为head节点

if (p == head && tryAcquire(arg)) { //这里p== head为true,接下来进入

// tryAcquire(arg)这个方法,tryAcquire(arg)方法前面分析过了,这里返回false;

setHead(node);

p.next = null; // help GC

failed = false;

return interrupted;

}

//   接下来会进入以下的逻辑:下面会分析这两个方法

if (shouldParkAfterFailedAcquire(p, node) &&

parkAndCheckInterrupt())

interrupted = true;

}

} finally {

if (failed)

cancelAcquire(node);

}

}

这里再次回顾下tryAcquire(arg) 方法:返回fasle

protected final boolean tryAcquire(int acquires) {    // acquires=1

final Thread current = Thread.currentThread(); //当前线程 thread-1

int c = getState();   //state=1

if (c == 0) {

if (!hasQueuedPredecessors() &&

compareAndSetState(0, acquires)) {

setExclusiveOwnerThread(current);

return true;

}

}

else if (current == getExclusiveOwnerThread()) {  // getExclusiveOwnerThread

获取到的线程是tryAcquire(int acquires)中设置的值 这里是 main; current=thread-1

int nextc = c + acquires;

if (nextc < 0)

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

setState(nextc);

return true;

}

return false;

}

}

1): acquireQueued(final Node node, int arg) 方法中for循环第一次执行shouldParkAfterFailedAcquire(p, node) 方法分析:源码如下:

入参:pred为前继节点,这里是head  node为当前节点

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {

int ws = pred.waitStatus;  // ws=0

if (ws == Node.SIGNAL)

return true;

if (ws > 0) {

do {

node.prev = pred = pred.prev;

} while (pred.waitStatus > 0);

pred.next = node;

} else {

compareAndSetWaitStatus(pred, ws, Node.SIGNAL);  //设置前继节点waitStatus= Node.SIGNAL,

}

return false;  //返回false;

}

For循环第二次执行 shouldParkAfterFailedAcquire(p, node)方法:

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {

int ws = pred.waitStatus;  // ws=-1   在第一次已经设置为-1

if (ws == Node.SIGNAL)   //返回true

return true;

if (ws > 0) {

do {

node.prev = pred = pred.prev;

} while (pred.waitStatus > 0);

pred.next = node;

} else {

compareAndSetWaitStatus(pred, ws, Node.SIGNAL);  //设置前继节点waitStatus= Node.SIGNAL,

}

return false;  //返回false;

}

返回true后,接下进入parkAndCheckInterrupt()这个方法:

源码分析如下:

private final boolean parkAndCheckInterrupt() {

LockSupport.park(this);  //阻塞当前线程

return Thread.interrupted(); // 返回线程的中断状态

}

LockSupport.park(this); //作用:前继线程节点的状态是 Node.SIGNAL;挂起当前线程;

Thread.interrupted(); //当前被挂起的线程被前继线程中断,返回线程的中断状态;

下面解释一个线程的行为:LockSupport.park(this)  线程被挂起:

当线程被挂起的时候唤醒的方式有两种:

1:unpark 的方式唤醒,前继节点线程使用完锁后,通过unpark方式唤醒当前线程

2:中断唤醒,其他线程通过 interrupt 中断当前线程

接下来继续分析:acquire(int arg)

public final void acquire(int arg) {

if (!tryAcquire(arg) &&

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

selfInterrupt();

}

这个时候的重点放在分析 selfInterrupt(); 这个方法上;

进入这个方法的条件是 当前线程被中断过,并且获取锁成功了;

static void selfInterrupt() {

Thread.currentThread().interrupt();  //当前线程产生一个中断,真正被唤醒

}

到此为止,ReentrantLock 的公平锁源码分析结束。

ReentrantLock 的公平锁源码分析的更多相关文章

  1. ReentrantLock之公平锁源码分析

    本文分析的ReentrantLock所对应的Java版本为JDK8. 在阅读本文前,读者应该知道什么是CAS.自旋. 本文大纲 1.ReentrantLock公平锁简介 2.AQS 3.lock方法 ...

  2. ReentrantLock之非公平锁源码分析

    本文分析的ReentrantLock所对应的Java版本为JDK8. 在阅读本文前,读者应该知道什么是CAS.自旋. 由于ReentrantLock的公平锁和非公平锁中有许多共同代码,本文只会对这两种 ...

  3. (转)ReentrantLock实现原理及源码分析

    背景:ReetrantLock底层是基于AQS实现的(CAS+CHL),有公平和非公平两种区别. 这种底层机制,很有必要通过跟踪源码来进行分析. 参考 ReentrantLock实现原理及源码分析 源 ...

  4. Redisson分布式锁学习总结:可重入锁 RedissonLock#lock 获取锁源码分析

    原文:Redisson分布式锁学习总结:可重入锁 RedissonLock#lock 获取锁源码分析 一.RedissonLock#lock 源码分析 1.根据锁key计算出 slot,一个slot对 ...

  5. ReentrantLock 公平锁源码 第0篇

    ReentrantLock 0 关于ReentrantLock的文章其实写过的,但当时写的感觉不是太好,就给删了,那为啥又要再写一遍呢 最近闲着没事想自己写个锁,然后整了几天出来后不是跑丢线程就是和没 ...

  6. ReentrantLock 公平锁源码 第1篇

    ReentrantLock 1 这篇还是接着ReentrantLock的公平锁,没看过第0篇的可以先去看上一篇https://www.cnblogs.com/sunankang/p/16456342. ...

  7. ReentrantLock实现原理及源码分析

    ReentrantLock是Java并发包中提供的一个可重入的互斥锁.ReentrantLock和synchronized在基本用法,行为语义上都是类似的,同样都具有可重入性.只不过相比原生的Sync ...

  8. 基于ReentrantLock的AQS的源码分析(独占、非中断、不超时部分)

    刚刚看完了并发实践这本书,算是理论具备了,看到了AQS的介绍,再看看源码,发现要想把并发理解透还是很难得,花了几个小时细分析了一下把可能出现的场景尽可能的往代码中去套,还是有些收获,但是真的很费脑,还 ...

  9. RedissonLock分布式锁源码分析

    最近碰到的一个问题,Java代码中写了一个定时器,分布式部署的时候,多台同时执行的话就会出现重复的数据,为了避免这种情况,之前是通过在配置文件里写上可以执行这段代码的IP,代码中判断如果跟这个IP相等 ...

随机推荐

  1. java.util.DualPivotQuickSort的实现

    DualPivotQuickSort汇集了多种排序算法,称之为DualPivotQuickSort并不合适.不同的排序算法有不同的使用场景.看懂此文件,排序算法就算彻底搞懂了. 本文只介绍有用的代码片 ...

  2. spark源码解析--Shuffle输出追踪者--MapOutputTracker

    Shuffle输出追踪者--MapOutputTracker 这个组件作为shuffle的一个辅助组件,在整个shuffle模块中具有很重要的作用.我们在前面一系列的分析中,或多或少都会提到这个组件, ...

  3. 实验吧简单的sql注入3

    今天早上起来发现有人评论说我没更新实验吧sql注入3,主要是因为前段时间都去做bugku去了 但是重做这道题发现以前的姿势不行了,exp()报错不再溢出,现在不能用这个姿势,所以这里重新整理了一遍思路 ...

  4. React Navigation 导航栏样式调整+底部角标消息提示

    五一佳节匆匆而过,有人选择在外面看人山人海,有人选择宅在家中度过五一,也有人依然坚守在第一线,致敬! 这是坚持学习react-native的第二篇文章,可能会迟到,但是绝不会缺席,这篇要涉及到的是re ...

  5. 【爬虫】网页抓包工具--Fiddler--Request和Response

    [爬虫]网页抓包工具--Fiddler Fiddler基础知识 Fiddler是强大的抓包工具,它的原理是以web代理服务器的形式进行工作的,使用的代理地址是:127.0.0.1,端口默认为8888, ...

  6. eclipse 搭建springboot项目pom.xml报错

    1. 报错信息 2. 解决方法 在pom.xml文件中加入maven版本修改 <maven-jar-plugin.version>3.1.1</maven-jar-plugin.ve ...

  7. Window 2003 IIS + MySQL + PHP + Zend 环境配置

    图文详解 下载 Windows 2003 Zend, PHP, PHPMyadmin 与 MySQL Windows 2003 安装包中包含了 Zend,PHP 5.2.17,PHPWind8.7 和 ...

  8. HAProxy的调度算法

      HAProxy通过固定参数balance指明对后端服务器的调度算法,该参数可以配置在listen或backend选项中.   HAProxy的调度算法分为静态和动态调度算法,但是有些算法可以根据参 ...

  9. 系统管理员必须知道的PHP安全实践

    Apache web 服务器提供了这种便利 :通过 HTTP 或 HTTPS 协议,访问文件和内容.配置不当的服务器端脚本语言会带来各种各样的问题.所以,使用 PHP 时要小心.以下是 25 个 PH ...

  10. Anaconda 安装 tensorflow 和 keras

    说明:此操作是在 Anaconda Prompt 窗口完成的 CPU版 tensorflow 的安装. 1.用 conda 创建虚拟环境 tensorflow python=3.6 conda cre ...