背景

JDK1.5引入的并发包提供了一系列支持中等并发的类,这些组件是一系列的同步器,几乎任一同步器都可以实现其他形式的同步器,例如,可以用可重入锁实现信号量或者用信号量实现可重入锁。但是,这样做带来的复杂性,开销,不灵活使其至多只能是个二流工程,且缺乏吸引力。如果任何这样的构造方式不能在本质上比其他形式更简洁,那么开发者就不应该随意地选择其中的某个来构建另一个同步器,所以JSR166建立了一个小框架-AQS(由Doug Lea设计),对这些同步器做了统一的抽象,为构造同步器提供了通用的机制,之后并发包中大部分同步器都基于AQS来实现。

注:本文通过ReentrantLock来窥探AQS的结构以及运行原理,因为AQS是并发包实现大部分同步器的框架,所以本文只对ReentrantLock相关方法做了解释说明,其他的方法在后面的文章中会继续做深入的解释

AQS设计

这是ReentrantLock中的内部类Sync的类图,图中可以看出Sync抽象类实现了AbstractQueuedSynchronizer。

ReentrantLock实现

ReentrantLock提供了非公平锁以及公平锁的能力,实现Lock接口,通过把功能实现委托给Sync同步器来实现。下面以非公平锁为例子,开始图解ReentrantLock类在调用lock方法时候的过程:

首先看下AQS的数据结构以及Node节点的结构

AQS的数据结构中state是最核心的变量,用来判断当前同步器是否有被线程占用,以及被同一个线程重入了多少次(重入锁实现的关键);

exclusiveOwerThread表示当前是哪个线程占用着同步器;

head是一个指向空的头结点的引用地址;

tail是一个指向等待同步器的最后一个节点的引用地址;

Node节点中最核心的是waitStatus,此处waitStatus的取值分别可以为:

  • 1表示等待的线程已经取消或者中断;
  • -1表示后一个节点需要唤醒,当前节点如果释放锁,则需要唤醒后继节点;
  • -2表示当前的节点是一个条件等待,即需要等待其他的条件满足才能够被加入到同步队列,等待被唤醒
  • -3表示下一个acquireShared应无条件传播(在读写锁中会遇到,后面会专门写文章分析读写锁)
  • 0表示初始状态

看完AQS的数据结构之后,我们再图解ReentrantLock非公平锁的lock方法,看下代码

整个lock流程如下(这里只画了大概的流程,细节太多了,后面对着代码实现图解里面会有体现):

图解ReentrantLock非公平锁lock方法

下面代码是我写这个图解例子用的,有兴趣可以自己尝试下,其中Thread.sleep(60*60*1000)为了让线程一致占有锁(即同步器),这样后面增加的对该同步器的抢占才会形成同步队列,方便分析。

1. 初始状态,没有线程获取到AQS同步器

2. 按照上面的代码线程thread5第一个发起了lock,所以同步器的state变为1,exclusiveOwnerThread=thread5,此时还没有竞争同步器,所以head以及tail都是null。

3. 由于Thread.sleep方法是不会释放锁的,所以thread5会一直抢占着锁。当线程thread6执行lock的时候,由于同步器的state=1,所以抢占失败,执行acquires(1)方法

进入acquire(1)方法之后,其实还会再尝试抢一次锁,不管有没有等待节点在排队,所以非公平锁其实一个线程进来之后有两次机会抢占锁,如果抢不到就乖乖去排队,下图中选中的代码就是第二次抢占机会。

如果两次抢占都失败以后就只能增加一个等待节点,然后添加到同步队列的尾部。

非公平锁是独占模式,所以创建等待节点的时候会传入Node.EXCLUSIVE,设置到nextWaiter中

而这个Node.EXCLUSIVE的值其实是null,nextWaiter在AQS中其实有三种含义

  • NULL:独占模式
  • SHARD:共享模式
  • 其他非空值:条件等待节点(调用Condition的await方法的时候)

节点创建成功之后需要把新创建的等待节点加入到同步队列的尾部

选中代码的意思就是如果已经有等待节点,那么直接插入到等待节点链表尾部(认为大部分情况下竞争其实并没有那么激烈,所以是可以直接插入成功的,所以代码如此设计),当然如果在高并发情况下插入失败了,那就执行常规的插入等待节点尾部的方法enq(node)(当没有等待队列的时候也需要执行enq方法,因为要初始化head以及tail节点)。

此处选中的代码就是当AQS的head以及tail为空的时候,初始化一个空节点,执行完以后是这样的结构

因为enq是在for的死循环里面的,所以会继续执行插入,直到成功插入到等待队列的尾部,再返回前继节点,那么线程thread6插入成功之后结构是这样的

到这里还没有结束,那么再继续再看下面的acquireQueued方法,代码如下

选中代码是一个死循环,可以认为是自旋,这里面可以分成两部分内容,如果node节点的前继节点是head节点(Empty Node),并且尝试把state从0设置为1,如果成功,就把当前节点设置为head节点(Empty Node),并且清空thread以及prev的值,这是在setHead方法中处理的。选中代码中的p.next=null,其实用意是前一个节点已经没有用了,把链接信息清空,再下一次垃圾回收的时候可以回收掉。

如果抢占锁没有成功,则会执行shouldParkAfterFailedAcquire方法,这个方法主要是用来设置前继节点的状态以及拿掉等待队列中已经取消的节点

新创建的节点加入到等待队列以后,其实还有一个事情没有做,就是要设置前继节点的waitStatus。

尾节点的waitStatus为默认值0,因为waitStatus的意义是为了标记后继节点的状态以及行为的。

所以for循环第一次进入shouldParkAfterFailedAcquire方法的时候,前继节点的waitStatus为0,会设置成-1,当再一次进入的时候会判断该值为-1,直接返回true。

中间的这段,就是从尾部开始往前,直到找到第一个小于等于0的等待节点,如下图:

大于0的值只有1,就是取消状态的节点,节点状态有4中,中间的节点状态不可能为0,因为每次添加进来之后都会被设置成-1,也不可能是-2,因为waitStatus值为-2的节点会进入条件等待队列,只有条件满足之后才会进入到同步队列,等待获取锁,同时把前继节点的waitStatus设置为-1,-3也是不可能的,因为-3是共享模式下才有,所以非公平锁独占模式下前继节点的值只可能为-1,0,1,最后的那段逻辑,直接设置前继节点的waitStatus为Node.SIGNAL(-1)就没有问题。

按照上面的逻辑处理完成之后,AQS的状态变成下面的样子

如果成功把新创建的线程加入到等待队列,那么需要让当前线程进入阻塞状态,执行方法parkAndCheckInterrupt,LockSupport就是前面文章写得AQS的基础

当该线程被唤醒的时候,会返回线程是否被中断,并清空中断标志,从这里就可以知道acquireQueued方法中的局部变量interrupted是干嘛用的了,就是判断线程被阻塞的时候有没有被中断,如果中断了,则返回之后执行selfInterrupt方法中断当前线程。

按照测试代码,最终形成的等待同步队列如下:

此时通过debug模式查看head以及后继节点如下:

其中线程thread5是在exclusiveOwnerThread变量中,如下图:

ReentrantLock公平锁

公平锁相对于非公平锁,其实就只有lock方法的区别,看下面的代码

lock方法中直接使用了acquire方法,相比于非公平锁的lock实现,公平锁少了第一次先尝试把state的值从0变1的过程。

再看tryAcquire方法也有点小区别,如果state=0,说明前一个执行的线程刚好执行完,但是后面还需要检查下是否后节点在同步队列排队,如果有节点在排队,那就不抢占了,直接加到同步队列尾部。

以上两点是公平锁实现和非公平锁实现的细微差别。

后续文章

AQS条件队列和同步队列的关系

透过ReentrantReadWriteLock窥探AQS

透过CountDownLatch窥探AQS

通过Semaphore窥探AQS

透过ReentrantLock窥探AQS的更多相关文章

  1. 透过CountDownLatch窥探AQS

    本文来自公众号“Kahuna”,可搜索Alitaba119,欢迎关注,转载请注明出处,非常感谢 “ A synchronization aid that allows one or more thre ...

  2. 透过 ReentrantLock 分析 AQS 的实现原理

    对于 Java 开发者来说,都会碰到多线程访问公共资源的情况,这时候,往往都是通过加锁来保证访问资源结果的正确性.在 java 中通常采用下面两种方式来解决加锁得问题: synchronized 关键 ...

  3. ReentrantLock 与 AQS 源码分析

    ReentrantLock 与 AQS 源码分析 1. 基本结构    重入锁 ReetrantLock,JDK 1.5新增的类,作用与synchronized关键字相当,但比synchronized ...

  4. 多线程学习笔记三之ReentrantLock与AQS实现分析

    目录 简介 AQS同步状态 AQS同步队列 ReentrantLock数据结构 公平锁的获取 tryAcquire(arg) addWaiter(Node mode) acquireQueued(fi ...

  5. 面经手册 · 第17篇《码农会锁,ReentrantLock之AQS原理分析和实践使用》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 如果你相信你做什么都能成,你会自信的多! 千万不要总自我否定,尤其是职场的打工人.如 ...

  6. 从ReentrantLock看AQS (AbstractQueuedSynchronizer) 运行流程

    从ReentrantLock看AQS (AbstractQueuedSynchronizer) 运行流程 概述 本文将以ReentrantLock为例来讲解AbstractQueuedSynchron ...

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

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

  8. 扒一扒ReentrantLock以及AQS实现原理

    提到JAVA加锁,我们通常会想到synchronized关键字或者是Java Concurrent Util(后面简称JCU)包下面的Lock,今天就来扒一扒Lock是如何实现的,比如我们可以先提出一 ...

  9. ReentrantLock 以及 AQS 实现原理

    什么是可重入锁?       ReentrantLock是可重入锁,什么是可重入锁呢?可重入锁就是当前持有该锁的线程能够多次获取该锁,无需等待.可重入锁是如何实现的呢?这要从ReentrantLock ...

随机推荐

  1. CSU-2007 Football Training Camp

    Football Training Camp 在一次足球联合训练中一共有n支队伍相互进行了若干场比赛. 对于每场比赛,赢了的队伍得3分,输了的队伍不得分,如果为平局则两支队伍各得1分. Input 输 ...

  2. requests中文页面乱码解决方案【转】

    requests中文页面乱码解决方案!   请给作者点赞 --> 原文链接 Python中文乱码,是一个很大的坑,自己不知道在这里遇到多少问题了.还好通过自己不断的总结,现在遇到乱码的情况越来越 ...

  3. 3 ways of including JavaScript in HTML

    Code written in JavaScript must be executed from a document written in HTML. There are three ways of ...

  4. 【SHELL】Linux下安装Oracle Client

    一.新建Oracle脚本存储目录并上传文件 [root@A04-Test-172]# mkdir -p /tmp/instance_oracle #新建存储目录 [root@A04-Test-172 ...

  5. 云中Active Directory是如何工作的?

    [TechTarget中国原创] 微软公司1999年在Windows Server 2000中引入Active Directory功能.后期的Windows Server版本中陆续进行改善提升,Win ...

  6. 【网易严选】iOS持续集成打包(Jenkins+fastlane+nginx)

    本文来自网易云社区 作者:孙娇 严选iOS客户端的现有打包方式是通过远程连接打包机执行脚本去打包,打完包会输出相应的ipa的二维码,扫一扫二维码可以安装,但是随着测试队伍的壮大,外包同学越来越多,在打 ...

  7. [oldboy-django][2深入django]老师管理 -- form表单如何生成多选框标签,多选框的默认值显示,以及多选框数据插入到数据库,多选框数据更改到数据库

    1 form表单如何生成多选框(包含了多选框可选择内容) - Form设置班级输入框为 select多选 - 多选 class TeacherForm(Form): name = fields.Cha ...

  8. (转)对称加密与非对称加密,以及RSA的原理

    一 概述 二对称加密和非对称加密 对称加密 非对称加密 区别 三RSA原理 整数运算 同余运算 当模数为合数n时 当模数为质数p的时候 离散对数问题 RSA原理 一 , 概述 在现代密码学诞生以前,就 ...

  9. 求职之路(拿到百度、美团、趋势科技、华为offer)

    求职之路(拿到百度.美团.趋势科技.华为offer) 版权所有:无缰之马chhuach(CSDN和博客源),转载请注明出处.CSDN地址http://blog.csdn.net/chhuach2005 ...

  10. python数据结构元组与集合

    元组 1.()来定义 2.有序,同列表 3.元组一旦创建,不能被修改 注:元组的标识是逗号,不是括号 元组的定义 a = (1,2) type(a) <class 'tuple'> 元组的 ...