- title: ReentrantLock 公平锁和非公平锁加锁和解锁源码分析(简述)
- date: 2021/8/16

文章目录

  • 一、ReentrantLock
    • 1. 构造函数
  • 二、ReentrantLock 公平锁的加锁(lock)过程
    • 1.1 调用ReentrantLock中的lock
    • 1.2 调用Sync中的抽象方法lock,具体实现由子类完成,这里是非公平锁
    • 1.3 调用NonfairSync中的 lock;
      • 1.3.1 acquire 方法:
        • 1.3.1.1 tryAcquire
        • 1.3.1.2 acquireQueued
          • 1.3.1.2.1 addWaiter()
          • 1.3.1.2.2 acquireQueued:
            • 1.3.1.2.2.1 shouldParkAfterFailedAcquire()
        • 1.3.1.3 selfInterrupt
  • 三、ReentrantLock 非公平锁和非公平锁的解锁(unlock)过程
  • 四、ReentrantLock 公平锁的加锁(lock)过程

一、ReentrantLock

ReentantLock 继承接口 Lock 并实现了接口中定义的方法,他是一种可重入锁,除了能完成
synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法

1. 构造函数

    private final Sync sync;//.../*** Creates an instance of {@code ReentrantLock}.* This is equivalent to using {@code ReentrantLock(false)}.*/public ReentrantLock() {sync = new NonfairSync();}/*** Creates an instance of {@code ReentrantLock} with the* given fairness policy.** @param fair {@code true} if this lock should use a fair ordering policy*/public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}

无参和有参构造函数,用于构造公平锁或非公平锁。

二、ReentrantLock 公平锁的加锁(lock)过程

参考示例代码:

class ReentrantLockExample {int           a    = 0;ReentrantLock lock = new ReentrantLock();public void writer() {lock.lock(); //获取锁try {a++;} finally {lock.unlock(); //释放锁}}public void reader() {lock.lock(); //获取锁try {int i = a;//……} finally {lock.unlock(); //释放锁}}
}

1.1 调用ReentrantLock中的lock

//ReentrantLock.java ReentrantLock/*** Acquires the lock.** <p>Acquires the lock if it is not held by another thread and returns* immediately, setting the lock hold count to one.** <p>If the current thread already holds the lock then the hold* count is incremented by one and the method returns immediately.** <p>If the lock is held by another thread then the* current thread becomes disabled for thread scheduling* purposes and lies dormant until the lock has been acquired,* at which time the lock hold count is set to one.*//*** 获取锁。** <p>如果没有被其他线程持有则获取锁并返回* 立即,将锁定保持计数设置为 1。** <p>如果当前线程已经持有锁,则持有* count 加一,方法立即返回。** <p>如果锁被另一个线程持有,那么* 当前线程被禁用以进行线程调度* 目的并处于休眠状态,直到获得锁,* 此时锁定保持计数设置为 1。*/public void lock() {sync.lock();}

1.2 调用Sync中的抽象方法lock,具体实现由子类完成,这里是非公平锁

//ReentrantLock.java Syncabstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = -5179523762034025860L;/*** Performs {@link Lock#lock}. The main reason for subclassing* is to allow fast path for nonfair version.*/abstract void lock();

1.3 调用NonfairSync中的 lock;

//ReentrantLock.java NonfairSync/*** Sync object for non-fair locks*/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() {//首先尝试进行CAS操作,将state值从0改为1if (compareAndSetState(0, 1))//如果CAS成功,设置exclusiveOwnerThread为当前变量//获取锁成功说明:1.state从0变为1;(从无竞争资源变为独占资源) //2. 此锁的exclusiveOwnerThread 为当前线程setExclusiveOwnerThread(Thread.currentThread());else//如果CAS失败,说明有其它的线程在抢占这个锁资源acquire(1);}//AbstractOwnableSynchronizer.java AbstractOwnableSynchronizer   /*** The current owner of exclusive mode synchronization.*/private transient Thread exclusiveOwnerThread;/*** Sets the thread that currently owns exclusive access.* A {@code null} argument indicates that no thread owns access.* This method does not otherwise impose any synchronization or* {@code volatile} field accesses.* @param thread the owner thread*/protected final void setExclusiveOwnerThread(Thread thread) {exclusiveOwnerThread = thread;}//AbstractQueuedSynchronizer.java AbstractQueuedSynchronizerpublic final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}
  • 首先执行 compareAndSetState(0, 1) ,尝试进行CAS操作,将state值从0改为1;

    • 如果CAS成功,调用 setExclusiveOwnerThread(Thread.currentThread()) ,设置exclusiveOwnerThread为当前变量;

      获取锁成功说明:

      1. state从0变为1;(从无竞争资源变为独占资源) ;

      2. 此锁的 exclusiveOwnerThread 为当前线程

    • 如果CAS失败,说明有其它的线程在抢占这个锁资源,执行acquire(1);

1.3.1 acquire 方法:

 //AbstractQueuedSynchronizer.java AbstractQueuedSynchronizer/*** Acquires in exclusive mode, ignoring interrupts.  Implemented* by invoking at least once {@link #tryAcquire},* returning on success.  Otherwise the thread is queued, possibly* repeatedly blocking and unblocking, invoking {@link* #tryAcquire} until success.  This method can be used* to implement method {@link Lock#lock}.** @param arg the acquire argument.  This value is conveyed to*        {@link #tryAcquire} but is otherwise uninterpreted and*        can represent anything you like.*/public final void acquire(int arg) {if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}

1.3.1.1 tryAcquire

//ReentranLock.java NonFairSyncprotected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}
        /*** Performs non-fair tryLock.  tryAcquire is implemented in* subclasses, but both need nonfair try for trylock method.*/final boolean nonfairTryAcquire(int acquires) {//获取当前线程final Thread current = Thread.currentThread();int c = getState();//如果c是0,锁处于空闲状态,使用CAS获取if (c == 0) {if (compareAndSetState(0, acquires)) {//如果CAS成功,设置当前线程是锁的持有者setExclusiveOwnerThread(current);return true;}}//如果当前线程是锁的持有者,这是ReentrantLock可重入的原因;else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;//可以看出源码设计者的周到,考虑到了锁溢出的情况if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");//将当前线程持有的锁+1setState(nextc);return true;}return false;}

1.3.1.2 acquireQueued

1.3.1.2.1 addWaiter()

addWaiter():将当前线程插入至队尾,返回在等待队列中的节点(就是处理了它的前驱后继)。

    private Node addWaiter(Node mode) {//把当前线程封装为node,指定资源访问模式Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;//如果tail不为空,把node插入末尾if (pred != null) {node.prev = pred;//此时可能有其他线程插入,所以使用CAS重新判断tailif (compareAndSetTail(pred, node)) {pred.next = node;return node;}}//如果tail为空,说明队列还没有初始化,执行enq()enq(node);return node;}

enq():将节点插入队尾,失败则自旋,直到成功。

    private Node enq(final Node node) {for (;;) {Node t = tail;//虽然tail==null才会执行本方法//但是可能刚好有其他线程插入,会导致//之前的判断失效,所以重新判断tail是否为空//队尾为空,说明队列中没有节点//初始化头尾节点if (t == null) { if (compareAndSetHead(new Node()))//初始化完成后,接着走下一个循环,//直到node正常插入尾部tail = head;} else {//下面就是链表的正常插入操作了node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}}
1.3.1.2.2 acquireQueued:

该函数表示将已经在队列中的node(每个线程对应一个node加到等待队列中,具体以后分析)尝试去获取锁否则挂起

    final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {//自旋final Node p = node.predecessor(); //获得该node/** 如果前置节点是head,表示之前的节点就是正在运行的线程,表示是第一个排队的* (一般讲队列中第一个是正在处理的,可以想象买票的过程,第一个人是正在买票(处理中),第二个才是真正排队的人);* 那么再去tryAcquire尝试获取锁,如果获取成功,说明此时前置线程已经运行结束,则将head设置为当前节点返回*/if (p == head && tryAcquire(arg)) {setHead(node);// help GC,将前置节点移出队列,这样就没有指针指向它,可以被gc回收p.next = null; // help GCfailed = false;//返回false表示不能被打断,意思是没有被挂起,也就是获得到了锁return interrupted;}/* shouldParkAfterFailedAcquire将前置node设置为需要被挂起,*注意这里的waitStatus是针对当前节点来说的,* 即是前置node的ws指的是下一个节点的状态*/if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}
1.3.1.2.2.1 shouldParkAfterFailedAcquire()

shouldParkAfterFailedAcquire():判断当前节点是否应该被挂起。下面涉及到的等待状态,这里再回忆一下,CANCELLED =1,SIGNAL =-1,CONDITION = -2,PROPAGATE = -3,0

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;if (ws == Node.SIGNAL)//前驱节点的状态是SIGNAL,说明前驱节点释放资源后会通知自己//此时当前节点可以安全的park(),因此返回truereturn true;if (ws > 0) {//前驱节点的状态是CANCLLED,说明前置节点已经放弃获取资源了//此时一直往前找,直到找到最近的一个处于正常等待状态的节点//并排在它后面,返回falsedo {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {//前驱节点的状态是0或PROPGATE,则利用CAS将前置节点的状态置//为SIGNAL,让它释放资源后通知自己//如果前置节点刚释放资源,状态就不是SIGNAL了,这时就会失败// 返回falsecompareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;}

parkAndCheckInterrupt():若确定有必要park,才会执行此方法。

    private final boolean parkAndCheckInterrupt() {//使用LockSupport,挂起当前线程LockSupport.park(this);return Thread.interrupted();}

1.3.1.3 selfInterrupt

selfInterrupt():对当前线程产生一个中断请求。能走到这个方法,说明acquireQueued()返回true,就进行自我中断。

    static void selfInterrupt() {Thread.currentThread().interrupt();}

到这里,获取资源的流程就走完了。

##1.4 获取非公平锁总结

  1. 在NonSync中的lock函数CAS尝试获取锁;

  2. 如果CAS失败,**tryAcquire()**尝试获取资源,该函数是一个钩子函数,在非公平锁中是nonfairTryAcquire();

    在nonfairTryAcquire中,会如果当前锁的状态是空闲的,进行CAS尝试获取锁,如果成功返回true,失败返回false; 否则,判断当前线程是否拥有该锁,进行一个可重入的操作,否则返回false;

  3. 如果tryAcquire返回false, 调用acquireQueued(addWaiter(Node.EXCLUSIVE), arg)),首先执行的是addWaiter函数;

    在addWaiter中,将当前线程插入至队尾(CAS操作),返回在等待队列中的节点(就是处理了它的前驱后继),如果tail为空或CAS失败,执行enq():将节点插入队尾,失败则自旋,直到成功。

    然后执行acquireQueued函数,进行一个自旋操作,获取前面的节点,判断是否是头节点head,如果是调用tryAcquire函数再次进行CAS获取锁和可重入的判断,如果成功,然后help GC,将前置节点移出队列,返回interrupted;否则,执行shouldParkAfterFailedAcquire(pred,node),如果返回true,再执行park,如果返回true,interrupted = true,继续自旋;

    shouldParkAfterFailedAcquire(pred,node):

    首先获取前驱节点的状态,

    如果前驱节点的状态是SIGNAL,说明前驱节点释放资源后会通知自己,此时当前节点可以安全的park(),因此返回true;

    如果前驱节点的状态是CANCLLED(1),说明前置节点已经放弃获取资源了,此时一直往前找,直到找到最近的一个处于正常等待状态的节点,将当前节点并排在它后面,返回false;

    如果前驱节点的状态是0或PROPGATE,则利用CAS将前置节点的状态置为SIGNAL,让它释放资源后通知自己,然后返回false;

    parkAndCheckInterrupt():若确定有必要park,才会执行此方法。使用LockSupport,挂起当前线程,并会返回当前线程是否处于中断状态;

  4. 如果acquireQueued返回true,执行selfInterrupt():对当前线程产生一个中断请求。能走到这个方法,说明acquireQueued()返回true,就进行自我中断。

就此,加锁过程完成;

三、ReentrantLock 非公平锁和非公平锁的解锁(unlock)过程

二者的解锁是相同的;

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

这个tryRelease也是一个钩子函数:尝试释放锁,彻底释放后返回true

//ReentrantLock.java ReentrantLockprotected final boolean tryRelease(int releases) {int c = getState() - releases;//释放锁必须保证当前线程是锁的持有者if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;//如果释放后状态值为0,则彻底释放,持有者置空if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;}

在释放锁的最后写volatile变量 state;

unparkSuccessor():尝试找到下一位继承人,就是确定下一个获取资源的线程,唤醒指定节点的后继节点。

    private void unparkSuccessor(Node node) {//如果状态为负说明是除CANCEL以外的状态,//尝试在等待信号时清除。int ws = node.waitStatus;if (ws < 0)compareAndSetWaitStatus(node, ws, 0);Node s = node.next;//下一个节点为空或CANCELLED状态//则从队尾往前找,找到正常状态的节点作为之后的继承人//也就是下一个能拿到资源的节点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);}

四、ReentrantLock 公平锁的加锁(lock)过程

//ReentrantLock.java FairLockfinal void lock() {acquire(1);}

公平锁和非公平锁获取锁只有在钩子函数tryAcquire的实现不同,和非公平锁相比,增加了一个判断当前节点是否是头节点;

        protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {//判断等待队列中是否有前驱节点,没有则尝试获取锁//hasQueuedPredecessors()返回false,表示没有前驱节点,当前线程就是头节点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;}

boolean hasQueuedPredecessors():判断当前线程是否位于CLH同步队列中的第一个。如果是则返回flase,否则返回true。

    public final boolean hasQueuedPredecessors() {//判断当前节点在等待队列中是否为头节点的后继节点(头节点不存储数据),//如果不是,则说明有线程比当前线程更早的请求资源,//根据公平性,当前线程请求资源失败。//如果当前节点没有前驱节点的话,才有做后面的逻辑判断的必要性Node t = tail; // Read fields in reverse initialization orderNode h = head;Node s;return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());}

参考连接:

  1. AQS源码详细解读 - 知乎 (zhihu.com)
  2. ReentrantLock源码详细解读 - 知乎 (zhihu.com)

ReentrantLock 公平锁和非公平锁加锁和解锁源码分析(简述)相关推荐

  1. java公平锁和非公平锁_java并发编程学习之再谈公平锁和非公平锁

    在java并发编程学习之显示锁Lock里有提过公平锁和非公平锁,我们知道他的使用方式,以及非公平锁的性能较高,在AQS源码分析的基础上,我们看看NonfairSync和FairSync的区别在什么地方 ...

  2. Java8 ReentrantLock 源码分析

    一.ReentrantLock 概述 1.1 ReentrantLock 简介 故名思义,ReentrantLock 意为可重入锁,那么什么是可重入锁呢?可重入意为一个持有锁的线程可以对资源重复加锁而 ...

  3. reentrantlock非公平锁不会随机挂起线程?_【原创】Java并发编程系列16 | 公平锁与非公平锁...

    本文为何适原创并发编程系列第 16 篇,文末有本系列文章汇总. 上一篇提到重入锁 ReentrantLock 支持两种锁,公平锁与非公平锁.那么这篇文章就来介绍一下公平锁与非公平锁. 为什么需要公平锁 ...

  4. 闲聊AQS面试和源码解读---可重入锁、LockSupport、CAS;从ReentrantLock源码来看公平锁与非公平锁、AQS到底是怎么用CLH队列来排队的?

    AQS原理可谓是JUC面试中的重灾区之一,今天我们就来一起看看AQS到底是什么? 这里我先整理了一些JUC面试最常问的问题? 1.Synchronized 相关问题以及可重入锁 ReentrantLo ...

  5. 浅谈ReentrantLock的公平锁和非公平锁的区别

    前言 最近在看java并发编程这本书,已经看了点ReentrantLock的源码,以及之前有面试官问,公平锁和非公平锁有啥区别,我就只是从源码层面说了一下区别,但在性能上也有区别,今天就来说道说道. ...

  6. ReentrantLock 实现原理(公平锁和非公平锁)

    使用 synchronized 来做同步处理时,锁的获取和释放都是隐式的,实现的原理是通过编译后加上不同的机器指令来实现. 而 ReentrantLock 就是一个普通的类,它是基于 AQS(Abst ...

  7. ReentrantLock之公平锁和非公平锁详解

    ReentrantLock是一个互斥锁,它具有synchronized相同的能力:但相比之下,ReentrantLock扩展性更强,比如实现了公平锁. 下面详细拆解下ReentrantLock的公平锁 ...

  8. 图解ReentrantLock公平锁和非公平锁实现

    概述 ReentrantLock是Java并发中十分常用的一个类,具备类似synchronized锁的作用.但是相比synchronized, 它具备更强的能力,同时支持公平锁和非公平锁. 公平锁: ...

  9. 【并发】ReentrantLock中公平锁和非公平锁的理解

    [前言] ReentrantLock 是在JavaSE5之后,并发包中新增了Lock接口用来实现锁功能,它提供了与synchronized关键字类似的同步功能.同时ReentrantLock也是重入锁 ...

最新文章

  1. svd 分解详细证明
  2. 特斯拉自动驾驶系统秘密,来自特斯拉AI总监爆料
  3. matlab绘制球面点,我需要在MATLAB的笛卡尔坐标中绘制球面方程的帮助
  4. python监控某个程序_写一个python的服务监控程序
  5. python numpy中bool变量转为1或0
  6. ffmpeg AVFilter介绍
  7. shell 传递参数
  8. 硬件学习笔记之稳压二极管
  9. 【C语言】初学者写基础代码的基本步骤
  10. ArcPad8新功能介绍
  11. f-droid_使用F-Droid在Android上开源
  12. Kafka 集群搭建
  13. Android仿高德地图打车的三段式BottomSheet
  14. 英语学习详细笔记(八)动名词
  15. Prisma(一):初识
  16. android xposed如何写,安卓Hook系列教程(一):xposed模块开发环境搭建
  17. MFRC522模块测试
  18. python绘制动态模拟图
  19. 教你如何零基础学习视频剪辑,干货满满
  20. set the request's mode to 'no-cors' to fetch the resource with CORS disa

热门文章

  1. 移植性问题のerror C2664: strcpy : 不能将参数 2 从 CString 转换为 const char *怎么回事?...
  2. flutter: 建树流程
  3. No.1 字符编码数据类型
  4. 深入浅出TensorFlow(二):TensorFlow解决MNIST问题入门
  5. 三级网络-不单单是学知识
  6. centos7下docker设置阿里云镜像(自定义阿里云镜像)
  7. c语言case语句块,JavaScript使用Switch语句来选择将要执行的代码块
  8. JUnit5 @AfterEach注解示例
  9. 05-03 docker 常用命令
  10. 在字符串中找出第一个只出现一次的字符