2019独角兽企业重金招聘Python工程师标准>>>

今天分析ReentrantLock类的源码,在看源码之前,先学习AQS(AbstractQueuedSynchronizer)类。

一、AQS

什么是AQS?

AQS是一个类,要实现多线程锁,可以继承该类,并重写其中的方法即可。该类也叫同步器。

同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态(state,标记线程的状态)。

同步器的设计是基于模板方法模式, 使用者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。

同步器需要重写的重要方法:

tryAcquire  独占锁获取

tryRelease   独占锁释放

tryAcquireShared  共享锁获取

tryReleaseShared  共享锁释放

isHeldExclusively 快速判断被线程独占

对同步状态进行更改,这时就需要使用同步器提供的3个方法(这三个方法不需要重写,只要子类进行调用,完成相应的操作)

getState 获取同步状态

setState 设置同步状态

compareAndSetState 原子的设置同步状态来进行操作。

二、结合ReentrantLock类来了解AQS的源码

ReentrantLock的结构

解释下,Sync是ReentrantLock类的内部类,且是实现AQS的类。ReentrantLock有两种锁,公平锁和非公平锁,分别继承了Sync类。下面以非公平锁为例,说明下AQS的源码。(确实AQS的源码比较难理解,我理解的可能也不是很深,毕竟才看了四五遍,我先把我的理解用图文并茂的形式展现出来)

ReentrantLock源码解析

final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}

非公平锁调用lock方法,是通过同步方法compareAndSetState(最终调用了unsafe类的方法)直接尝试设置state为1,意思就是如果没有锁,这个方法就会返回true,执行setExclusiveOwnerThread方法,将当前线程设置为锁的持有者。如果之前有锁,就执行acquire这个方法。(state:0表示没有锁,1表示已经有锁)

假设,这时有个thread1调用非公平锁的lock方法,因为state的初始状态位0,很明显,执行了setExclusiveOwnerThread,将state设置为了1,并将当前线程设置为锁的持有者。然后又来了一个线程thread2调用lock获取锁,设这时候的thread1并没有释放锁。明显compareAndSetState设置不成功,返回false,然后执行acquire方法。这个方法是AQS类里。

public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}

这个if写的,比较高大上,好多源码都是这么写的。可以看成:

if(!tryAcquire(arg))
{Node node = addWaiter(Node.EXCLUSIVE);if(acquierQueued(node,arg))selfInterrupt();
}

这时候,thread2先执行tryAcquire方法。那tryAcquire在哪呢?上面说了,子类要实现tryAcquire方法。所以执行的是在NonFairSync里面。

protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}

我们现在看下nonfairTryAcquire方法

final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();//如果没有锁,就尝试获取锁if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}//判断当前线程是否为锁的持有者,支持锁的重入else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}

thread2调用该方法,这时候state为1(因为thread1在持有锁),并且thread2<>thread1,所以返回false。

然后再回到acquire方法。

public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}

因为tryAcquire返回false,即!tryAcquire为true。这时候thread2执行addWaiter方法。

private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;if (pred != null) {/***和enq中的else中的方法一致,都是将节点插入到同步队列末尾**/node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node;}

首先,将thread2封装成Node类(这个Node类定义在AQS里面,是同步队列中的节点),因为thread1并没有创建队列,所以tail为null,即空的同步队列。所以thread2执行enq方法。

private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // Must initializeif (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}}

这是一个自旋循环。。。。

通过上面分析,同步队列为空,即t=null。所以,thread2新建了一个空的Node节点,该节点是“假”节点,然后通过CAS操作设置了线程的阻塞队列的head节点就是我们刚才new出来的那个空的node节点,并将tail和head指向这个“假”节点,由此可知,该同步队列是一个带头节点的同步队列。至此,第一次循环结束,此时同步队列的状态为:

然后进行第二循环,t这时候不为空了,所以执行else分支。

一句一句的来:

node.prev = t;将尾部tail节点赋值给我们传递进来的节点Node的前驱节点,此时的结构如下:

compareAndSetTail(t, node),通过同步方法,将node设置为尾节点。

t.next=node;将原来的tail节点的后继指向node节点。

此时跳出自旋,返回当前节点。

public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}

在看这个acquire方法,执行完addWaiter方法后,然后thread2执行acquireQueued方法。

final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}

哎,头疼,又是一个自旋,不过没有关系,把同步队列的结构搞清楚,就很容易分析。

首先,获取thread2对应的节点的前驱节点,这时,就是head节点,即“假”节点。如果前驱节点是head节点,就调用之前的tryAcquire方法,尝试获取锁。因为thread1没有释放锁,很明显tryAcquire方法返回false。因此thread2方法执行下面的if方法。看下shouldParkAfterFailedAcquire方法

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;//Node.SIGNAL表示-1if (ws == Node.SIGNAL)/** This node has already set status asking a release* to signal it, so it can safely park.*/return true;if (ws > 0) {/** Predecessor was cancelled. Skip over predecessors and* indicate retry.*/do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {/** waitStatus must be 0 or PROPAGATE.  Indicate that we* need a signal, but don't park yet.  Caller will need to* retry to make sure it cannot acquire before parking.*/compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;}

获取前驱节点的waitStatus值。-1:表示当前节点正处于等待signal唤醒。>0:表示前驱节点应该被移除。其他情况:将调用同步方法将前驱的waitStatus设置为-1

因为head节点的waitStatus=0,所以设置head的waitStatus为-1,然后返回false。此时,结束了acquireQueued的第一次循环。现在执行第二次循环,和第一循环一样,没有执行第一个if,继续执行shouldParkAfterFailedAcquire方法。这时ws不再是0了,而是-1了。因此返回true。然后调用acquireQueued中的parkAndCheckInterrupt方法。

private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();}

由代码可以看出,调用了LockSupport的park方法。

这里介绍下LockSuppor类。

LockSupport定义了一组的公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能,而LockSupport也成为构建同步组件的基础工具。LockSupport定义了一组以park开头的方法用来阻塞当前线程,以及unpark(Thread thread)方法来唤醒一个被阻塞的线程。

由此可知,thread2被阻塞起来,不在执行。。。。

假设这时,thread1还没有释放锁(感觉时间过了半个世纪,其实这些操作,以计算机的速度,我们看了还没有这些操作差不多),此时thread3线程调用lock方法。因为thread1还有没有释放锁,所以调用AQS的acquire方法。进而调用tryAcquire方法,同样返回false,所以继续调用addWaiter方法。

private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node;}

因为此时同步队列中有thread2方法,所以执行if里面的方法,直接将thread3对应的节点插入到同步队列的末尾,然后返回。继续调用acquireQueued方法。

final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}

进入自旋,第一次循环,很明显thread3节点的前驱节点是thread2,不是head,所以执行shouldParkAfterFailedAcquire方法。因为thread2结点的waitStatus为0,所以通过同步方法设置waitStatus为-1,然后返回false。此时,第一次循环结束。执行第二次循环,hread3的前继任然不是thread2,所以继续执行shouldParkAfterFailedAcquire方法。此时thread2的waitStatus为-1,所以直接返回true。然后thread3执行parkAndCheckInterrupt方法。然后将thread3线程进行阻塞。

此时thread1终于结束了,调用unlock释放锁。

public void unlock() {sync.release(1);}

进而调用AQS的release放释放锁。

public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}

首先,调用NonFairSync中的tryRelease方法。

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;}

现将当前的state-1,如果为0,表示没有线程持有锁了,将持有锁的线程设置为 null,设置state为0,放回true。如果state不等0,表示有重入锁,设置state(不为0),然后返回false。设thread1没有重入锁,所以该方法返回true。然后进行if判断,如果同步队列有结点,并且头结点的waitStatus不为0,则调用unparkSuccessor方法。

private void unparkSuccessor(Node node) {/** If status is negative (i.e., possibly needing signal) try* to clear in anticipation of signalling.  It is OK if this* fails or if status is changed by waiting thread.*/int ws = node.waitStatus;if (ws < 0)compareAndSetWaitStatus(node, ws, 0);/** Thread to unpark is held in successor, which is normally* just the next node.  But if cancelled or apparently null,* traverse backwards from tail to find the actual* non-cancelled successor.*/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.unpark(s.thread);}

第一步,将头结点的waitStatus设为0,然后判断head的下一个结点,即thead2结点是否为null,如果不为空,thread2的waitStatus值是否>0。很明显,不执行这个if,那就执行下一个if,将thread2进行唤醒。我们知道,thread2在acquireQueued方法里被阻塞,然后唤醒后,继续执行。进行第下一次的循环。

final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}

然后调用第一if,执行if里面的代码。即将thread2结点设为头结点,让原来的head结点从同步队列中清除。然后返回false。至此,thread2处于运行状态。。。。

公平锁和非公平锁的区别:

在公平锁中FairSync中,tryAcquire方法:

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;}

对比非公平锁发现,

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

这个判断多了一个!hasQueuedPredecessors方法,判断同步队列中是否还有等待的线程,如果没有,当前线程就获取state,如果有,就返回false,意味着将当前线程插入到同步队列末尾。而非公平锁就不看有没有队列,直接尝试获取锁,如果没有获取到才插入到队列的末尾。

可中断的获取锁

当调用lockInterruptibly时:

public void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1);}

然后调用AQS中的acquireInterruuptibly方法。

public final void acquireInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();if (!tryAcquire(arg))doAcquireInterruptibly(arg);}

首先查看当前线程的中断标记为,如果为true,就抛异常。

private void doAcquireInterruptibly(int arg)throws InterruptedException {final Node node = addWaiter(Node.EXCLUSIVE);boolean failed = true;try {for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}
final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}

对比lock获取锁的acquireQueued方法和lockInterruptibly获取锁的doAcquireInterruptibly方法,发现当线程处于等待锁的阻塞中,如果被中断,doAcquireInterruptibly直接抛出异常,而acquireQueued只是将中断标记位设为true,具体中断不中断看线程的心情。。。。。。。

转载于:https://my.oschina.net/littlestyle/blog/1605294

多线程(三)之ReentrantLock源码解析相关推荐

  1. 面试官系统精讲Java源码及大厂真题 - 32 ReentrantLock 源码解析

    32 ReentrantLock 源码解析 才能一旦让懒惰支配,它就一无可为. 引导语 上两小节我们学习了 AQS,本章我们就要来学习一下第一个 AQS 的实现类:ReentrantLock,看看其底 ...

  2. 线程池解析(三)——Worker源码解析

    相关文章 线程池解析(一)--概念总结 线程池解析(二)--execute.addWorker源码解析 线程池解析(三)--Worker源码解析 线程池解析(四)--submit源码解析(Runnab ...

  3. synchronized 和 reentrantlock 区别是什么_JUC源码系列之ReentrantLock源码解析

    目录 ReentrantLock 简介 ReentrantLock 使用示例 ReentrantLock 与 synchronized 的区别 ReentrantLock 实现原理 Reentrant ...

  4. 【并发编程】 --- Reentrantlock源码解析5:再探不可中断性 + 线程unpark后诡异的Thread.interrupted()判断

    文章目录 1 想要读懂这篇文章必须要拥有的前置知识 2 想写这篇文章的原因 3 困扰我很久的Reentrantlock源代码1 --- 貌似无用的变量failed 4 困扰我很久的Reentrantl ...

  5. 【vuejs深入三】vue源码解析之二 htmlParse解析器的实现

    写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. 昨天博主分析了一下在vue中,最为基础核心的api,parse函数,它的作用是将vue的模板字符串转换成ast,从而 ...

  6. java容器三:HashMap源码解析

    前言:Map接口 map是一个存储键值对的集合,实现了Map接口的主要类有以下几种 TreeMap:用红黑树实现 HashMap:数组和链表实现 HashTable:与HashMap类似,但是线程安全 ...

  7. 轻触开源(三)-Gson项目源码解析_贰

    2019独角兽企业重金招聘Python工程师标准>>> 转载请注明出处:https://my.oschina.net/u/874727/blog/750473 Q:102525062 ...

  8. java condition_死磕 java同步系列之ReentrantLock源码解析(二)

    (手机横屏看源码更方便) 问题 (1)条件锁是什么? (2)条件锁适用于什么场景? (3)条件锁的await()是在其它线程signal()的时候唤醒的吗? 简介 条件锁,是指在获取锁之后发现当前业务 ...

  9. 【多线程】ThreadPoolExecutor类源码解析----续(二进制相关运算)

    前言 在之前阅读 ThreadPoolExecutor 源码的时候,发现代码里用到了一些二进制相关的位运算之类的代码,看起来有些费劲了,所以现在大概总结了一些笔记,二进制这东西吧,不难,就跟数学一样, ...

最新文章

  1. 23种设计模式(一)单例模式
  2. 冰点文库下载器停止工作解决办法
  3. grep检索关键字的命令_linux系统中java线上问题常用排查命令
  4. pyhton 把文字放入图片里_藏在京城老字号里的六道功夫菜!久违了!!
  5. Result Maps collection already contains value for ***的问题
  6. 前端学习(3009):vue+element今日头条管理--登录中的loding
  7. 边栏实现过程中,导入style.css没用
  8. 将VMware与SoftICE基于网络的远程调试功能相结合
  9. Gtest:死亡测试
  10. IceSword冰刃使用方法
  11. RISCV 向量指令集和NICE接口学习笔记
  12. C语言:离散数学8.1.5 笛卡尔积
  13. APICloud AVM框架 开发视频会议APP
  14. 信用风险计量模型汇总
  15. 左右手坐标转换 四元数
  16. 可以使用中文作为变量名_一套智慧校园CAD设计方案,可以作为投标技术文件使用...
  17. 黑龙江省双鸭山市谷歌高清卫星地图下载
  18. 【数电实验】触发器及其应用
  19. 无人机数据处理工作站完美配置方案
  20. 【转】Excel中的Vlookup等函数用法

热门文章

  1. sas univariate 结果解释_PROC UNIVARIATE过程
  2. 树梅派kali界面_使用树莓派和kali Linux打造便携式渗透套件
  3. C段 192.168.1.15/28与192.168.1.16/28的区别
  4. 加速数据分析,这12种高效Numpy和Pandas函数为你保驾护航
  5. linux下统计所有py文件的代码多少行
  6. mysql buff cache_Centos7 cache/buff过高处理方法
  7. 在dreamweaver mx中它只能对html文件可以进行编辑,【职称计算机考试网页制作历年试题及答案二】- 环球网校...
  8. 天翼云从业认证课后习题(第一章和第二章)
  9. LeetCode - 4. 寻找两个正序数组的中位数
  10. 密码学之hill密码