前言

在AQS系列(一)中我们一起看了ReentrantLock加锁的过程,今天我们看释放锁,看看老Lea那冷峻的思维是如何在代码中笔走龙蛇的。

正文

追踪unlock方法:

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

很简单的一行,调用了release方法,参数为1,继续跟踪发现不管是公平锁还是非公平锁调用的都是AbstractQueuedSynchronizer中的release方法:

  1. public final boolean release(int arg) {
  2. if (tryRelease(arg)) {
  3. Node h = head;
  4. if (h != null && h.waitStatus != 0)
  5. unparkSuccessor(h);
  6. return true;
  7. }
  8. return false;
  9. }

此方法看起来简单,却暗含杀机。

1、首先看if中的判断方法tryRelease

  1. protected final boolean tryRelease(int releases) {
  2. int c = getState() - releases; // 计算出释放锁之后的state值
  3. if (Thread.currentThread() != getExclusiveOwnerThread())
  4. throw new IllegalMonitorStateException();
  5. boolean free = false;
  6. if (c == 0) { // c==0说明要释放锁了
  7. free = true;
  8. setExclusiveOwnerThread(null); //在释放之前将独占线程置为空
  1. }
    setState(c); // 将state置为0,此处没用cas操作,因为没必要,反正在此之前state都大于0,不会被其他线程操作,只有当前线程能操作
    return free;
    }

此方法的实现逻辑在ReentrantLock类的Sync内部类中,即公平锁和非公平锁公用,相信理解起来比较轻松。

2、再看里面的if判断条件 h != null && h.waitStatus != 0

注意此时h是head,队列头。我们先要搞清楚这两个判断条件所表示的意思,h!=null说明队列不是空的,而h.waitStatus != 0又是什么意思呢?回顾一下上一篇的最后第二个方法 shouldParkAfterFailedAcquire,当时讲这个方法时其实描述的不是很清楚,这次重新结合释放锁的场景回顾一下。下面先将该方法粘贴出来(注释中的两个2表示执行一次这个方法只会走一个2的逻辑):

  1. private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
  2. int ws = pred.waitStatus; // 1、正常情况进到这里ws是0,pred可能是head,也可能只是node前面另一个排队的任务
  3. if (ws == Node.SIGNAL)
  4. // 3、如果是-1了,就返回true,进入后面park当前线程
  5. return true;
  6. if (ws > 0) {
  7. do {
  8. // 2、如果是大于0,说明pred线程已经被取消,则继续往前遍历,直到从后往前找到第一个不大于0的节点,然后互相设置指针
  9. node.prev = pred = pred.prev;
  10. } while (pred.waitStatus > 0);
  11. pred.next = node;
  12. } else {
  13. // 2、是0的话进这里,设置成-1,注意是将pred(即当前node的前一个节点)设置成-1。即如果一个节点ws是-1,那么它后面一定至少还有一个node(就是这个方法中的node)
  14. compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
  15. }
  16. return false;
  17. }

waitStatus>0只有一种情况-线程被取消了(状态值为1)。当线程被取消时就要舍弃掉它,继续往前遍历。

回顾完上述的方法,再看h.waitStatus != 0,我们可以知道,waitStatus != 0表示等待后面还有排队的node(可能是正常状态也可能是已取消的状态),这时就要去唤醒下一个正常状态的线程,进入unparkSuccessor方法。

3、unparkSuccessor 方法代码

  1. private void unparkSuccessor(Node node) {
  2. int ws = node.waitStatus;
  3. if (ws < 0)
  4. compareAndSetWaitStatus(node, ws, 0);
  5.  
  6. Node s = node.next;
  7. if (s == null || s.waitStatus > 0) {
  8. s = null;
  9. for (Node t = tail; t != null && t != node; t = t.prev)
  10. if (t.waitStatus <= 0)
  11. s = t;
  12. }
  13. if (s != null)
  14. LockSupport.unpark(s.thread);
  15. }

该方法用于唤醒当前线程的下一个有效任务,入参node为head节点。首先如果ws为-1则通过CAS设置为0;然后判断node的下一个节点是不是空,或者是不是已经被取消(ws大于0表示已经被取消);如果满足条件,则从后往前遍历找到从前往后数的第一个ws小于等于0的node节点,唤醒这个节点的线程。此处的for循环用的比较有意思,用了一种类似于while循环的格式来用for循环,可见老Lea不拘一格的思维方式。

此处最后一行unpark方法执行之后,就会进入系列(一)中的最后一个方法的第3行代码(如下所示),继续执行下一个线程的加锁过程,进入下一次轮回。

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

附加:公平锁与非公平锁的源码理解

在上一篇文章中未讲到公平锁和非公平锁的区别,在这里统一进行一下总结:

在释放锁的过程中,公平锁和非公平锁的处理流程是一样的,都是从队列的头往后遍历挨个唤醒等待的线程。

在加锁的过程中,有两个不同的地方。第一个是在lock方法中,公平锁代码:

  1. final void lock() {
  2. acquire(1);
  3. }

非公平锁代码:

  1. final void lock() {
  2. if (compareAndSetState(0, 1))
  3. setExclusiveOwnerThread(Thread.currentThread());
  4. else
  5. acquire(1);
  6. }

可以看到非公平锁直接先用CAS尝试获取一下锁,不用排队。这就是第一个非公平的地方。

第二个不同的地方,是acquire方法中的tryAcquire方法实现不同,公平锁的tryAcquire方法:

  1. protected final boolean tryAcquire(int acquires) {
  2. final Thread current = Thread.currentThread();
  3. int c = getState();
  4. if (c == 0) {
  5. if (!hasQueuedPredecessors() &&
  6. compareAndSetState(0, acquires)) {
  7. setExclusiveOwnerThread(current);
  8. return true;
  9. }
  10. }
  11. else if (current == getExclusiveOwnerThread()) {
  12. int nextc = c + acquires;
  13. if (nextc < 0)
  14. throw new Error("Maximum lock count exceeded");
  15. setState(nextc);
  16. return true;
  17. }
  18. return false;
  19. }

可以看到当c==0时公平锁会先通过hasQueuedPredecessors方法判断队列前面有没有排队的。

非公平锁的实现:

  1. protected final boolean tryAcquire(int acquires) {
  2. return nonfairTryAcquire(acquires);
  3. }
  4.  
  5. final boolean nonfairTryAcquire(int acquires) {
  6. final Thread current = Thread.currentThread();
  7. int c = getState();
  8. if (c == 0) {
  9. if (compareAndSetState(0, acquires)) {
  10. setExclusiveOwnerThread(current);
  11. return true;
  12. }
  13. }
  14. else if (current == getExclusiveOwnerThread()) {
  15. int nextc = c + acquires;
  16. if (nextc < 0) // overflow
  17. throw new Error("Maximum lock count exceeded");
  18. setState(nextc);
  19. return true;
  20. }
  21. return false;
  22. }

当c==0时,非公平锁是直接用CAS尝试获取加锁。这是第二个非公平的地方。

好了,ReentrantLock的加锁和释放锁过程基本就这些了,这周末继续搞JUC!

AQS系列(二)- ReentrantLock的释放锁的更多相关文章

  1. java并发系列(三)-----ReentrantLock(重入锁)功能详解和应用演示

    1. ReentrantLock简介 jdk中独占锁的实现除了使用关键字synchronized外,还可以使用ReentrantLock.虽然在性能上ReentrantLock和synchronize ...

  2. AQS系列(四)- ReentrantReadWriteLock读写锁的释放锁

    前言 继续JUC包中ReentrantReadWriteLock的学习,今天学习释放锁. 一.写锁释放锁 入口方法 public void unlock() { sync.release(1); } ...

  3. 面试 LockSupport.park()会释放锁资源吗?

    (手机横屏看源码更方便) 引子 大家知道,我最近在招人,今天遇到个同学,他的源码看过一些,然后我就开始了AQS连环问. 我:说说AQS的大致流程? 他:AQS包含一个状态变量,一个同步队列--bala ...

  4. 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念

    深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 一.AQS框架简介 AQS诞生于Jdk1.5,在当时低效且功能单一的synchroni ...

  5. 【Java并发编程实战】----- AQS(二):获取锁、释放锁

    上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...

  6. 死磕 java同步系列之ReentrantLock源码解析(二)——条件锁

    问题 (1)条件锁是什么? (2)条件锁适用于什么场景? (3)条件锁的await()是在其它线程signal()的时候唤醒的吗? 简介 条件锁,是指在获取锁之后发现当前业务场景自己无法处理,而需要等 ...

  7. Java - "JUC" ReentrantLock释放锁

    Java多线程系列--“JUC锁”04之 公平锁(二) 释放公平锁(基于JDK1.7.0_40) 1. unlock() unlock()在ReentrantLock.java中实现的,源码如下: p ...

  8. 高级java必会系列二:多线程经常使用的3个关键字:synchronized、ReentrantLock、volatile

    系列一讲解了多线程,本章讲解多线程开发中经常使用到的3个关键字synchronized.ReentrantLock.volatile. 一.synchronized 互斥锁,即操作互斥,并发线程过来, ...

  9. AQS系列(一)- ReentrantLock的加锁

    前言 AQS即AbstractQueuedSynchronizer,是JUC包中的一个核心抽象类,JUC包中的绝大多数功能都是直接或间接通过它来实现的.本文是AQS系列的第一篇,后面会持续更新多篇,争 ...

随机推荐

  1. .netcore之DI批量注入(支持泛型) - xms

    一旦系统内模块比较多,按DI标准方法去逐个硬敲AddScoped/AddSingleton/AddTransient缺乏灵活性且效率低下,所以批量注入提供了很大的便捷性,特别是对于泛型的服务类,下面介 ...

  2. JVM初体验

    一.设计堆内存溢出异常:OutOfMemoryError: public class Main { public static void main(String[] args) { List<D ...

  3. Mybatis自定义TypeHandler解决特殊类型转换问题

    我们知道,Java和MySQL中的数据类型是不同的,Java中除了基本数据类型,还有对象. 有时候使用MySQL存储数据,或者从MySQL中读取数据时,会有一些特殊需求

  4. Deepin 下 使用 Rider 开发 .NET Core

    Deepin 下 使用 Rider 开发 .NET Core 国产的 Deepin 不错,安利一下. Deepin 用了也有一两年,也只是玩玩,没用在开发上面.后来 Win10 不太清真了,就想着能不 ...

  5. 源码学习系列之SpringBoot自动配置(篇二)

    源码学习系列之SpringBoot自动配置(篇二)之HttpEncodingAutoConfiguration 源码分析 继上一篇博客源码学习系列之SpringBoot自动配置(篇一)之后,本博客继续 ...

  6. springboot配置springMVC

    /** * @ClassName MvcConfigure * @Description SpringMVC配置 * @Author JAGNG * @Date 2019/10/28 10:23 ** ...

  7. MyBatis批量更新动态sql

    <update id="updateDataKetState"> update ${tablespace}.IDEA_DATAKEY_STATE <trim pr ...

  8. Geometry 判断几何是否被另一个几何/线段分割成多段

    如下图,如何判断几何多边形A被多边形B,切割为多段几何? 几何A被几何B切割 1. 获取几何A与几何B的交集C var intersectGeometry = new CombinedGeometry ...

  9. 自学python的高效学习方法【python秘籍】

    随着互联网的发展,数据科学概念的普及,Python火得一塌糊涂,为此很多小伙伴想学这门语言,苦于没有正确的学习方法,大部分都放弃了,所以我想总结下经验来帮助大家高效学完python技术!第一.首先学习 ...

  10. 如何在C++中使用boost库序列化自定义class ?| serialize and deserialize a class in cpp with boost

    本文首发于个人博客https://kezunlin.me/post/6887a6ee/,欢迎阅读! serialize and deserialize a class in cpp Guide how ...