一、AQS简介

AQS全称AbstractQueuedSynchronizer,是java并发包中的一个类,该类更像是一个框架,提供了一些模板方法供子类实现,从而实现了不同的同步器,如下图所示。ReentrantLock,ReentrantReadWriteLock,ThreadPoolExecutor这些常见类都使用了AQS。
 

 
以下是AQS的成员变量:
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
static final long spinForTimeoutThreshold = 1000L;
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
private static final long waitStatusOffset;
private static final long nextOffset;
 
       看到这里大致能猜到AQS内部维护了一个双向链表,head,tail分别指向头尾,事实上,Node节点封装了尝试获取锁的线程对象,所有尝试获取锁的线程组成了一个链表,在公平锁情况下,例如ReentrantLock中的AQS子类FairSync,每次都是按照顺序头部节点先被唤醒并尝试获取锁。
        state 是同步状态位,具体是否能够获取锁就是通过修改state来实现,下面会有具体代码分析。
        spinForTimeoutThreshold相当于一个阈值,在一些提供等待时间的获取锁操作时,例如ReentrantLock. tryLock(long timeout, TimeUnit unit)方法,在判定是否需要阻塞线程时,如果时间小于spinForTimeoutThreshold,则不会被阻塞,用于快速响应一些等待时间很短的获取锁操作。
       其他成员变量是关于CAS操作的,AQS的很多操作都是基于CAS原子操作的,以确保线程安全。

二、ReentrantLock简介

ReentrantLock是根据AQS实现的独占锁,提供了两个构造方法,如下图所示:
 

 
ReentrantLock有三个内部类:Sync,NonfairSync,FairSync,继承关系如下:
 

 
ReentrantLock提供两种类型的锁:公平锁,非公平锁。分别对应FairSync,NonfairSync。默认实现是NonFairSync。
ReentrantLock提供了lock(),lockInterruptibly(),tryLock(),tryLock(long timeout, TimeUnit unit)四种获取锁的方式。

三、非公平锁lock源码分析

下面从ReentrantLock.lock()简述一下其源码实现
 

 
lock()方法内部是委托给sync变量来实现的,下面是NonfairSync的lock方法源码:
 

 
       在NonfairSync的lock方法体里,首先尝试修改state的值,上面说到,AQS很多操作都是基于CAS的,在这里,设置state的期望值是0(没有线程持有锁时的状态),修改值为1,如果成功,则返回true,并且设置持有锁的线程为当前线程。乐观情况下,lock方法获取锁操作到这里就结束了。
      但是,很多情况下并不是那么乐观,如果compareAndSetState操作失败,就会进入到acquire方法:
 

 
acquire方法在AQS类,这里,首先会调用tryAcquire方法,该方法的具体实现在NonfairSync中:

 
tryAcquire方法内部调用了Sync的nonfairTryAcquire方法:

 
       在Sync的nonfairTryAcquire方法体里,如果state为0,会再做一次compare and set操作,尝试修改state的值为1。
       如果state不为0,判断当前线程是否是持有独占锁的线程,如果是,将state值加上acquires(传入的是1),这里就是ReentrantLock可重入的内部实现。
       如果方法返回true,那么获取锁的操作结束,如果返回false,回到AQS的acquire()方法内部:

 
会继续调用方法addWaiter,返回结果后,执行 acquireQueued方法。下面看一下addWaiter方法:

 
       如上图所示,addWaiter方法的功能是将当前线程封装成Node,并加入到AQS链表尾部,参数mode是传入的Node.EXCLUSIVE,代表实例化的node是独占模式,而非共享模式,注意如果pred == null表示内部队列还没有初始化,则会调用enq(node)。或者pred != null 但是在compareAndSetTail失败时,也会调用enq(node)。例如,同时有多个线程node尝试加入到链表末尾,就会存在失败的可能。
进入到enq方法内部:

 
       这个方法的最外层是一个大的for循环,并且是一个死循环。出口返回条件只有一个:成功加入到链表的末尾。前面讲到,在链表为空或者添加node到链表末尾失败时会进入到enq方法,这里首先判断tail是否为空,如果为空,实例化一个空的Node节点,并且tail和head都指向这个空的Node,如果不为空,将node加入到链表末尾,如下图所示:
 

 
将node成功加入到链表中后,回到AQS的acquire()方法内部,开始执行acquireQueued方法:
 

 
       这里也是一个循环,循环体内首先获取node的前一个节点,即node.prev指向的节点p,接着判断p是否是head节点,如果是head节点,会尝试获取锁,tryAcquire方法在上面已经分析过。获取锁成功之后,sethead(node)会把node节点置为头节点,p.next = null将之前的head节点指向断掉,帮助jvm触发GC。最后返回当前线程在获取锁过程中是否曾经被中断。
       如果node.prev不是头节点,不会尝试获取锁,这也就是AQS内部链表的作用,会从链表的头部开始尝试获取锁,达到一个FIFO的作用。获取锁失败或者node.prev不是头节点,则会执行shouldParkAfterFailedAcquire:

 
       Node.SIGNAL说明该节点准备好被唤醒,若节点没有设置为该状态,线程不会阻塞。
shouldParkAfterFailedAcquire方法有三个作用:1、若pred.waitStatus状态位大于0,说明这个节点已经取消了获取锁的操作,doWhile循环会递归删除掉这些放弃获取锁的节点。2、若状态位不为Node.SIGNAL,且没有取消操作,则会尝试将状态位修改为Node.SIGNAL。3、状态位是Node.SIGNAL,表明线程是否已经准备好被阻塞并等待唤醒。
       最终,只有在pred.waitStatus已经等于Node.SIGNAL时才会返回true。其他情况返回false,然后acquireQueued会继续循环。
       在shouldParkAfterFailedAcquire返回true之后,acquireQueued方法体内继续执行parkAndCheckInterrupt():
 

该方法调用LockSupport.park()方法使线程阻塞。注意,ReentrantLock.lock()获取锁阻塞就是在这一步实现。阻塞的线程在其他线程释放锁之后会被LockSupport.unpark()唤醒。LockSupport.park(),LockSuppoert.unpark()最终都是调用了UNSAFE的native方法,这里不做分析。整个ReentrantLock.lock方法就分析到这里,下面看一下unlock操作。

四、非公平锁unlock源码分析

ReentrantLock.unlock()方法内部同样是交给sync的release实现:
 

 
sync.release()方法调用是在父类AQS中, release方法会先调用子类Sync的tryRelease()方法,如下图所示:
 

 
如下是子类Sync.tryRelease()的源码:
 

 
       首先获取state值,并减去releases,这里releases为1。若当前线程非独占锁拥有线程,抛出异常。若减去1后state为0,说明可以唤醒其他线程尝试获取锁,将free设置为true并返回,设置独占锁拥有者为null。
       如果不为0,设置state为减少后的值并且返回false,这样的话,就不会有后面唤醒其他线程的操作。所以,需要注意可重入的锁,在获取锁的时候,调用了多少次lock方法,释放锁时,就需要调用多少次unlock方法。
       在返回值为true之后,回到父类的release方法,最终会调用unparkSuccessor()方法:

 
       在unparkSuccessor方法中,会获取node.next,使变量s = node.next。若s不为空且状态位小于0,则满足唤醒条件,执行LockSupport.unpark()唤醒线程。否则执行for循环递归查找到离head最近的一个待唤醒节点唤醒,节点唤醒之后会继续执行获取锁的操作,上面已经做过分析,这里不做赘述。

五、其他

由于篇幅原因,只讨论了非公平锁的实现,在这里大概讲一下公平锁的“公平”体现在哪里,根据上面讲到的,非公平锁获取锁有两个地方:
1、在NonfairSync.lock方法体入口处就直接获取锁然后退出方法;
2、加入到链表中,每次链表头部的节点被唤醒,接着尝试获取锁。虽然头部节点被唤醒之后,会尝试获取锁,但可能会有线程在在NonfairSync.lock方法体入口处不进入链表就直接取得了锁。
       而在公平锁中,如下图所示,hasQueuedPredecessors会首先判断链表中是否有排队线程,没有排队线程才会尝试获取锁,否则加入到链表排队。严格保证了所有线程都是按照链表顺序先入先出的获取锁。
 

网易云捕-高效的APP质量跟踪平台

转载于:https://blog.51cto.com/11846530/1856871

ReentrantLock及AQS浅谈相关推荐

  1. 线程CAS AQS浅谈

    多线程 每一个进程都有一个独立的进程 1.线程:线程是一条执行路径,每一个线程互不影响: 多线程:多线程在一个进程中,有多条不同的执行路径,并执行,目的为了提高程序效率 在一个进程中,一定会有主线程 ...

  2. java 多线程同步_浅谈Java多线程(状态、同步等)

    Java多线程是Java程序员必须掌握的基本的知识点,这块知识点比较复杂,知识点也比较多,今天我们一一来聊下Java多线程,系统的整理下这部分内容. 一.Java中线程创建的三种方式: 1.通过继承T ...

  3. 浅谈Java锁,与JUC的常用类,集合安全类,常用辅助类,读写锁,阻塞队列,线程池,ForkJoin,volatile,单例模式不安全,CAS,各种锁

    浅谈JUC的常用类 JUC就是java.util.concurrent-包下的类 回顾多线程 Java默认有几个线程? 2 个 mian.GC Java 真的可以开启线程吗? 开不了,点击源码得知:本 ...

  4. Android 系统(104)---浅谈ANR及log分析ANR

    浅谈ANR及log分析ANR 一:什么是ANR ANR:Application Not Responding,即应用无响应 二:ANR的类型 ANR一般有三种类型: 1:KeyDispatchTime ...

  5. Redission实现分布式锁完美方案 以及 Lua 脚本浅谈

    Redission实现分布式锁完美方案 以及 Lua 脚本浅谈 文章目录 Redission实现分布式锁完美方案 以及 Lua 脚本浅谈 前言 常见分布式锁方案对比 分布式锁需满足四个条件 Redis ...

  6. 浅谈Java多线程机制

    浅谈Java多线程机制 (-----文中重点信息将用红色字体凸显-----) 一.话题导入 在开始简述Java多线程机制之前,我不得不吐槽一下我国糟糕的IT界技术分享氛围和不给力的互联网技术解答深度. ...

  7. 浅谈MySQL存储引擎-InnoDBMyISAM

    浅谈MySQL存储引擎-InnoDB&MyISAM 存储引擎在MySQL的逻辑架构中位于第三层,负责MySQL中的数据的存储和提取.MySQL存储引擎有很多,不同的存储引擎保存数据和索引的方式 ...

  8. 【大话设计模式】——浅谈设计模式基础

    初学设计模式给我最大的感受是:人类真是伟大啊!单单是设计模式的基础课程就让我感受到了强烈的生活气息. 个人感觉<大话设计模式>这本书写的真好.让貌似非常晦涩难懂的设计模式变的生活化.趣味化 ...

  9. 学校计算机机房好处,浅谈学校计算机机房维护

    浅谈学校计算机机房维护    现在的学校机房都配置了数量较多的计算机,而且机房的使用非常频繁.对于怎样维护好计算机,特别是计算机软件系统,对广大计算机教师来说是一个很重要且非常现实的问题.下面就本人在 ...

  10. java 中的单元测试_浅谈Java 中的单元测试

    单元测试编写 Junit 单元测试框架 对于Java语言而言,其单元测试框架,有Junit和TestNG这两种, 下面是一个典型的JUnit测试类的结构 package com.example.dem ...

最新文章

  1. 车载DMI linux系统,基于嵌入式的CTCS3级车载DMI系统的设计与实现
  2. 常见浏览器兼容问题、盒模型2种模式以及css hack知识讲解
  3. java面向对象程序设计第三版耿祥义pdf_java基础知识干货——封装
  4. 高级c++头文件bits/stdc++.h
  5. Hash函数及其应用
  6. cocos2d-lua-win
  7. java chat_使用 Java 创建聊天客户端-1
  8. Java的文件流定义,java文件流的问题!急
  9. Git 切换提交历史节点
  10. ZeroTier内网穿透工具配置
  11. python __setattr__和__getattr__
  12. 计算机网络复习-物理层
  13. 内连接和外连接的区别--举例
  14. 阿里巴巴分布式调度引擎tbschedule实战四tbschedule的配置使用
  15. Android 给透明png图片添加白色底色(修改像素点的形式)
  16. 小案例之点击网页任意位置出现小爱心
  17. 2008nian元旦
  18. 面试高频算法题补充系列:木棍切割问题
  19. Neurosim的manual细读(四)
  20. 腾讯新闻电脑客户端 v4.3.2 官方pc版

热门文章

  1. atitit.闭包的概念与理解attilax总结v2 qb18.doc
  2. Atitit.ati  str  字符串增强api
  3. paip.项目文件同步-分支和合并总结V2012.9.23
  4. 袁承兴:【译】Async/Await(一)——多任务
  5. (转)200亿美元比特币找不到主人,这个邪恶职业一夜爆火
  6. Julia : 小s与关于绝对路径的转义方式
  7. 云钉一体,支撑5亿用户1900万企业背后的技术复盘
  8. 阿里云边缘计算又要放大招了,7月24开发者大会现场揭秘,邀你参加!
  9. 阿里云郑晓:浅谈GPU虚拟化技术(第一章)
  10. 【回归预测】基于matlab Tent混沌映射改进的麻雀搜索算法SSA优化BP神经网络回归预测【含Matlab源码 1707期】