大量源码注释警告,请耐心阅读

AQS

实现了代码块的并发控制,通过内置的FIFO双向队列来完成线程的排队工作

基于模板方法模式设计,因此子类只需对五个方法,进行部分重写

4个try:{独占 | 共享} + {获取 | 释放}  ; 1个独占判断//2个独占式的获取、释放 同步状态
protected boolean tryAcquire(int arg)
protected boolean tryRelease(int arg) //2个共享式的获取、释放 同步状态
protected int tryAcquireShared(int arg)
protected int tryReleaseShared(int arg) //当前同步器是否在独占模式下被线程占用
protected int isHeldExclusively(int arg)

重要变量、常量

static final class Node {/** 线程因为中断或者等待超时,需要从等待队列中取消等待 */static final int CANCELLED =  1;/** 该结点被释放或被cancel后,需要唤醒后续结点  */static final int SIGNAL    = -1;/** 结点在等待队列中  */static final int CONDITION = -2;/*** 表示下一次共享状态获取将会传递给后继结点获取这个共享同步状态  */static final int PROPAGATE = -3;//当前线程等待状态 , 取值为上述4个 + 取值为0:表示剩余情况volatile int waitStatus;// 前驱、后继结点引用volatile Node prev;volatile Node next;// 本结点保存的线程    volatile Thread thread;...省略部分代码
}
//同步队列头节点的引用,懒加载,若存在,其waitStatus不能为CANCELLED
private transient volatile Node head;
//同步队列尾节点的引用,也是懒加载的。     head、tail都在enq方法中初始化
private transient volatile Node tail;
//AQS核心:同步状态
private volatile int state;

排它锁获取 :acquire() 忽略中断

//在独占模式下获取锁,并且忽略中断
public final void acquire(int arg) {if (!tryAcquire(arg) &&  // tryAcquire方法由子类重写
两个方法:addWaiter  、 acquireQueuedacquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 因为是忽略中断的前提下获取锁
//满足if,即线程是被中断唤醒的,需要selfInterrupt设置中断状态位(但其实无法保证中断)selfInterrupt();}

addWaiter()

作用:在同步队列增加一个Node,更新为tail,返回更新后的tail

private Node addWaiter(Node mode) {//从上文可知,mode参数:Node.EXCLUSIVE = null ,/* 相关构造函数;Node(Thread thread, Node mode) {    this.nextWaiter = mode;this.thread = thread;}*///所以node的waitStatus为0,有效的成员变量一个thread,用于记录线程Node node = new Node(Thread.currentThread(), mode);Node pred = tail;// tail不为空时,尝试快速地插入并更新tail结点if (pred != null) {node.prev = pred;//一遍CAS,所以叫“快速地”if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}//代码执行到此,1.tail为null,未初始化   2.CAS失败//上述if其实是快速版的enqenq(node);return node;
}
特殊:未初始化时,在此处实现了head跟tail的初始化(体现了懒加载)
private Node enq(final Node node) {for (;;) { //死循环保证CAS成功Node t = tail;if (t == null) { //tail为空,证明未初始化
//设置哨兵(因为空构造方法无设置任何参数),后更新head、tail,指向同一Nodeif (compareAndSetHead(new Node()))tail = head;} else{  //插入并更新tail结点,成功后返回node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}}

acquireQueued(final Node node, int arg)

作用:在同步队列中寻找node的前驱结点。如果前驱结点是头节点的话就会再次尝试取获取锁,否则会先设置自己的waitStatus为-1,然后调用LockSupport的方法park自己

final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;//死循环for (;;) {//获取前驱结点final Node p = node.predecessor();//如果前驱结点是头节点,就再次尝试获取锁,因为头结点的线程可能完成了 !!!:这里体现了队列中排队处理的特点(FIFO),因为若前驱结点不是头结点,则只能循环:(挂起、唤醒)if (p == head && tryAcquire(arg)) {// 成功,表明头结点的线程已经执行完成,当前线程不需要挂起// 更新头结点,无需CAS,因为只有一条线程可执行到该代码setHead(node);p.next = null; // 标记GCfailed = false;
注:这是唯一return , 所以除非tryAcquire抛异常了,否则无法跳出死循环
但不会占用大量CPU,因为下面会挂起线程,因此死循环起效仅限于本线程可用的期间//返回是否被中断唤醒return interrupted;}// 查找并更新合法前驱结点,见下if (shouldParkAfterFailedAcquire(p, node) &&//挂起线程,唤醒后返回boolean:是否中断唤醒parkAndCheckInterrupt())//interrupted = true;}} finally {// fail为false是正常逻辑,说明成功获取锁并return interrupted// 若fail为true,证明抛出了异常,则取消获取锁的状态。//该异常来源于 子类实现的tryAcquire(当然本类实现的tryAcquire也是直接抛出异常)if (failed)cancelAcquire(node);}
}
shouldParkAfterFailedAcquire
// 保证前一个结点一定是SIGNAL,这样当前结点才能被安全挂起
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;if (ws == Node.SIGNAL)return true;
/*     需要注意的是其余情况下,会返回false,然后 回到上层继续死循环原因:1. ws>0 的情况下,更新前驱结点后,可能是SIGNAL也可能不是,再写判断代码冗杂2. else情况下,CAS没有死循环,无法保证成功上述两点都比较牵强,因为都是可以加代码可以解决的事真正原因:在操作期间,头结点的线程可能完成了,因此返回上层再判断一遍。因为挂起/唤醒线程都是需要用户态跟内核态的切换的,相当损耗资源。倒不如说1.2 的情况是写源码的大佬故意而为之,提高性能
*/    // ws > 0 ,只有一种情况:CANCELLED = 1if (ws > 0) {// 删除(并更新)所有CANCELLED 的前驱结点do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {// 剩余情况:注释说只有  0 or PROPAGATE  , CONDITION ?//直接 CAS(一次,非死循环)  前驱节点为SIGNALcompareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;}
parkAndCheckInterrupt
private final boolean parkAndCheckInterrupt() {//挂起线程LockSupport.park(this);//等待中断 或 unpark方法来唤醒 //根据唤醒的原因来操作,也正是这里完成了“忽略中断”的任务// 1.中断唤醒,则直接重置状态位,并返回true// 2.unpark唤醒,返回false,状态位不变// Thread.interrupted:返回并重置(设为false)中断状态位,就完成了上述两点return Thread.interrupted();
}

排它锁释放:release()

public final boolean release(int arg) {if (tryRelease(arg)) {  // 子类重写Node h = head;// !=0, 说明当前结点未被释放if (h != null && h.waitStatus != 0)// 唤醒后继结点unparkSuccessor(h);return true;}return false;}

unparkSuccessor

//唤醒node后续的第一个合法结点
private void unparkSuccessor(Node node) {int ws = node.waitStatus;if (ws < 0)//若waitStatus小于0,则CAS置0 ,作为已释放的标志// 这里没有死循环,无法保证CAS成功,但releasecompareAndSetWaitStatus(node, ws, 0);//获得node的后继结点Node s = node.next;//判断后继结点是否已经被移除,或者waitStatus>0, 即为CANCELLEDif (s == null || s.waitStatus > 0) {//结点已被移除,置nulls = null;//从尾部结点到node间逆序寻找,虽是逆序寻找,但目标是//找到离node最近的,不为null且waitStatus<=0,即非CANCELLED的结点for (Node t = tail; t != null && t != node; t = t.prev)if (t.waitStatus <= 0)s = t;  // 注意,并无break,循环继续}//if (s != null)//唤醒结点中的线程LockSupport.unpark(s.thread);
}

本文完,有误欢迎指出

参考资料:
深入浅出java同步器AQS

深入理解Java中的AQS

AQS排它锁的获取acquire、释放release详解相关推荐

  1. android pcm 音量_Android中实时获取音量分贝值详解

    基础知识 度量声音强度,大家最熟悉的单位就是分贝(decibel,缩写为dB).这是一个无纲量的相对单位,计算公式如下: 分子是测量值的声压,分母是参考值的声压(20微帕,人类所能听到的最小声压).因 ...

  2. php读音量大小,Android_Android中实时获取音量分贝值详解,基础知识 度量声音强度,大 - phpStudy...

    Android中实时获取音量分贝值详解 基础知识 度量声音强度,大家最熟悉的单位就是分贝(decibel,缩写为dB).这是一个无纲量的相对单位,计算公式如下: 分子是测量值的声压,分母是参考值的声压 ...

  3. 计算机自动获取IP地址流程详解

    计算机自动获取IP地址流程详解 你知道吗? 我们在日常生活中直接插上网线接口就可以直接上网,不需要拨号上网也不需要设置IP地址.那么我们在日常是怎么获得IP地址的呢? 这里就需要介绍一种服务是由Int ...

  4. PHP的$_SERVER['HTTP_HOST']获取服务器地址功能详解

    PHP的$_SERVER['HTTP_HOST']获取服务器地址功能详解 uchome的index文件中的二级域名功能判断,使用了php的$_SERVER['HTTP_HOST'],开始对这个不是很了 ...

  5. Js获取图片主色调,近似色,互补色,以及根据图片颜色获取主题配色方案详解、插件。

    Js获取图片主色调,近似色,互补色,以及根据图片颜色获取主题配色方案详解.插件. **应用场景:**在很多时候,前端开发过程中需要动态的获取图片的主要的颜色值,并根据主色调去调整主题样式的颜色或者模拟 ...

  6. php 获取手机特征码,【新人学习】按键精灵获取数字特征码实例详解

    按键精灵获取数字特征码实例详解 运行环境:分辨率:1440x900 色深:32位 操作系统:Windows XP 按键精灵版本:7.00.3730 @兄弟工程师01未评2009/3/19//做这东西主 ...

  7. php获取总共内存_PHP获取内存使用情况详解

    本篇将详解php获取内存使用情况. PHP内置函数memory_get_usage()能返回当前分配给PHP脚本的内存量,单位是字节(byte).在WEB实际开发中,这些函数非常有用,我们可以使用它来 ...

  8. php study 直接显示代码_PHP获取文件大小的方法详解(附视频)

    本篇文章主要给大家介绍PHP获取文件大小以及封装获取正常大小的具体方法. 对于初入门的PHP新手来说,PHP获取文件大小这个功能实现,或许有一定的难度.但是相信新手小白们在看过本篇文章介绍后,一定能轻 ...

  9. python获取屏幕文字_详解:四种方法教你对Python获取屏幕截图(PyQt , pyautogui)...

    前言: 今天为大家带来的内容是详解:四种方法教你对Python获取屏幕截图(PyQt , pyautogui)本文具有不错的参考意义,希望能够帮助到大家! Python获取电脑截图有多种方式,具体如下 ...

最新文章

  1. linux svn 撤销del,svn delete-删除文件和目录的实例
  2. 合约 cd 模式_CD的完整形式是什么?
  3. 719. Find K-th Smallest Pair Distance
  4. http response 返回 没有内容_HTTP 教程2
  5. SpringBoot 精通系列-如何优雅地使用Mybatis的XML配置
  6. 文件夹去掉git版本控制_git 从版本控制中删除文件及.gitignore的用法
  7. 医院耗材管理系统开发_2
  8. 10款好用的谷歌chrome浏览器插件、扩展程序,用起来很爽哦
  9. 外资公司章程标准范本
  10. Studio 3T 破解 mogodb
  11. Skyscrapers (hard version)
  12. 美国服务器怎么怎么修改密码,RAKsmart美国服务器更改密码的简单方法
  13. 【程序人生】机灵鹤六月份的月度总结
  14. 3451. 易位构词
  15. 中科大2021计算机考研分数线,中国科学技术大学2021年考研复试各科分数线_中国科大考多少分能进复试-聚创中国科大考研网...
  16. 某单位分配到一个C类网络地址,其网络号为218.7.8.0,现在该单位共有4个不同的部门,每个部门最多25台主机,要求进行子网划分
  17. 线性dp:DP9 环形数组的连续子数组最大和
  18. 颜色 /About Color --图形学的B面(二)
  19. pgpool-II(二)pgpool-II+repmgr(master/slave)+balance+pgpool
  20. 【送书活动】豆瓣9.0,这本书YYDS

热门文章

  1. 制作简易的个人主页(代码笔记)
  2. java车次信息_车次查询示例代码
  3. Java生成图片验证码(有点仿QQ验证码的意思)
  4. C++Primer5th 第十九章 特殊工具与技术
  5. 95后腾讯T3-2 晒出工资单:狠补了这个,真香…
  6. 南京邮电大学通达学院物理实验题库答案
  7. 51单片机程序开发入门知识
  8. 推荐20位活跃在GitHub上的国内技术大牛
  9. Java并发编程实战读书笔记三
  10. 重要消息!国务院明确电子票允许作为报销凭证