源码解析之AQS源码解析
要理解Lock首先要理解AQS,而要理解并发类最好的方法是先理解其并发控制量不同值的含义以及该类运作流程,然后配合一步步看源码。
该类有一个重要的控制量是WaitStates,节点的状态值。
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1; //该节点被取消了
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1; //该节点后续节点需要被唤醒
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2; //该节点进入了等待队列,即Condition的队列里
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3; //共享节点,该节点进锁后会释放锁,。
AQS流程图:


Condition与Lock配合:

源码分析:核心方法 aquaire和release及他们方法体里使用到的方法。
public final void acquire(int arg) {
if (!tryAcquire(arg) && //如果tryacquire失败 且 队列里获取节点成功 且被中断过
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();//当前线程中断 interrupt()
//说下中断的事,1、如果在acquireQueued过程线程被interrupt,如果线程没进入等待状态,并不会中断线程。只是改变interrupt变量
// 且被传回到这里(因为是用interrupted,所以返回true之后又把线程的interrupt变量设为false)。然后selfinterrupt,将interrupt变量设为true。
// 2、如果线程被park了然后被interrupt,则被唤醒,循环一次发现还是阻塞又进入park等待状态。直到被unpark,interrupt参数为true传回到这里。
//然后interrupt参数又变回false(受interrupted影响),selfinterrupt则又把它设为true。
}
private Node addWaiter(Node mode) {//mode表示该节点的共享/排他性,null为排他,不为null为共享
Node node = new Node(Thread.currentThread(), mode);//将线程加入创建一个新节点
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {//尾节点不为空,新节点连到队列尾部
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);//上一步失败通过enq入队
return node;
}
final boolean acquireQueued(final Node node, int arg) {//从队列里尝试获取锁
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();//前节点
if (p == head && tryAcquire(arg)) {//前节点为头节点,且尝试获取锁获取到了。则清理前节点,
setHead(node); //head指向现节点
p.next = null; // help GC
failed = false;
return interrupted;
}
//shouldParkAfterFailedAcquire如果前节点为-1则返回true,如果0(初始),-3(共享)则设为-1,如果1则
// 找到前面<-0的节点连他后面
//parkAndCheckInterrupt 阻塞当前线程,并返回interrupted状态(阻塞则设置失败),并清除中断状态
if (shouldParkAfterFailedAcquire(p, node) &&//前节点状态为-1 return true
parkAndCheckInterrupt()) //中断状态为true return true
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);//节点取消获取,队列中删除节点,
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {//获取失败后判断是否暂停该线程
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)//前节点处于当前节点后面的节点需要被唤醒状态
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {//前节点处于取消状态
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {//当前节点找到前面状态<=0的节点连他后面。
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {//如果前节点不处于取消状态,则设为signal -1.(0或-3设为-1)
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//阻塞该线程,至此该线程进入等待状态,等着unpark和interrupt叫醒他
return Thread.interrupted();//叫醒之后返回该线程是否在中断状态, 并会清除中断记号。
}
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
node.thread = null;//thread指向null
// Skip cancelled predecessors
Node pred = node.prev;//当前节点找到前面status<=0的节点连它后面
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
node.waitStatus = Node.CANCELLED;//status状态设为1 取消状态
// If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) {//节点为末尾,把找到的status<0节点后面节点都切掉
compareAndSetNext(pred, predNext, null);
} else {//节点不为末尾,前节点连上当前节点后节点
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
//节点不为末尾,找到status<=0的前节点不是头节点且该节点线程不是null、且status为<=0的前节点状态为-1(不是(-3,0)则设为-1)
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {//节点不为末尾,status<=0的前节点是头节点 或status<=0的前节点线程为null,
unparkSuccessor(node);//专门给头节点用的启动继任者函数,只有前status<=0节点是头节点,且
//现节点后有节点才需unpark后续节点。(因为前节点可能唤醒的是当前线程,如果你删除当前节点
//不unpark后面节点可能就停止工作。如果你是尾节点,那无所谓,反正你后面也没线程需要unpark)
}
node.next = node; // help GC
}
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//释放操作,启动继任者
return true;
}
return false;
}
private void unparkSuccessor(Node node) {//启动继任者线程
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)//-2,-3等待队列或共享锁线程改为0 空白状态
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {//如果后节点为null或waitstatus>0(线程取消状态)
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)//找到节点后面status<=0的节点启动它
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
源码解析之AQS源码解析的更多相关文章
- 并发编程之:AQS源码解析
大家好,我是小黑,一个在互联网苟且偷生的农民工. 在Java并发编程中,经常会用到锁,除了Synchronized这个JDK关键字以外,还有Lock接口下面的各种锁实现,如重入锁ReentrantLo ...
- 浩哥解析MyBatis源码(十一)——Parsing解析模块之通用标记解析器(GenericTokenParser)与标记处理器(TokenHandler)
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6724223.html 1.回顾 上面的几篇解析了类型模块,在MyBatis中类型模块包含的 ...
- JAVA常用集合源码解析系列-ArrayList源码解析(基于JDK8)
文章系作者原创,如有转载请注明出处,如有雷同,那就雷同吧~(who care!) 一.写在前面 这是源码分析计划的第一篇,博主准备把一些常用的集合源码过一遍,比如:ArrayList.HashMap及 ...
- Spring源码情操陶冶-自定义节点的解析
本文承接前文Spring源码情操陶冶-DefaultBeanDefinitionDocumentReader#parseBeanDefinitions,特开辟出一块新地来啃啃这块有意思的骨头 自定义节 ...
- Spring源码情操陶冶-ComponentScanBeanDefinitionParser文件扫描解析器
承接前文Spring源码情操陶冶-自定义节点的解析,本文讲述spring通过context:component-scan节点干了什么事 ComponentScanBeanDefinitionParse ...
- Spring源码情操陶冶-AnnotationConfigBeanDefinitionParser注解配置解析器
本文承接前文Spring源码情操陶冶-自定义节点的解析,分析spring中的context:annotation-config节点如何被解析 源码概览 对BeanDefinitionParser接口的 ...
- Spring源码情操陶冶-PropertyPlaceholderBeanDefinitionParser注解配置解析器
本文针对spring配置的context:property-placeholder作下简单的分析,承接前文Spring源码情操陶冶-自定义节点的解析 spring配置文件应用 <context: ...
- SpringMVC源码情操陶冶-InterceptorsBeanDefinitionParser拦截器解析器
解析mvc:interceptors节点 观察下InterceptorsBeanDefinitionParser的源码备注 /** * {@link org.springframework.beans ...
- JDK源码及其他框架源码解析随笔地址导航
置顶一篇文章,主要是整理一下写过的JDK中各个类的源码及其他框架源码解析的文章,方便自己随时阅读也方便网友朋友们阅读与指正 基础篇 从为什么String=String谈到StringBuilder和S ...
随机推荐
- WKWebView 加载本地HTML随笔
一天的时间 解决两个坑~~ 1.加载不出来本地HTML 的JS CSS 样式,问题是引用到项目中 是用的group 这个是错的 直接上图 就知道了 像上图一样,加入相对路径即可,因为如果使用了gro ...
- 内存泄露java.lang.OutOfMemoryError: PermGen space解决方法
PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader时就会被 ...
- Js/如何操作div下面的span元素或者是img之类的标签元素
$("div[name='MatTypeName']").click(function (e) { $("div[name='MatTypeName']").e ...
- PHP面试题学习
PHP 开发工程师笔试试卷 姓名 :__________ 第一部分为必答题,第二.三部分任选其一回答 一. PHP 开发部分 1.合并两个数组有几种方式,试比较它们的异同. 2.请写一个函数来检查用户 ...
- xamarin android 报错 Could not load assembly 'Xamarin.Android.Support.v7.AppCompat
严重性 代码 说明 项目 文件 行 禁止显示状态 错误 Exception while loading assemblies: System.IO.FileNotFoundException: Cou ...
- 使用vue自定义简单的消息提示框
<style scoped> /** 弹窗动画*/ a { text-decoration: none } .drop-enter-active { /* 动画进入过程:0.5s */ t ...
- kafka-producer配置
kafka-producer版本对比 Kafka的producer的API根据版本的不同分为kafka0.8.1.X之前的 kafka.javaapi.producer.Producer.以及之后版本 ...
- Django学习笔记之模板
模板 模板介绍 在之前的章节中,视图函数只是直接返回文本,而在实际生产环境中其实很少这样用,因为实际的页面大多是带有样式的HTML代码,这可以让浏览器渲染出非常漂亮的页面. 目前市面上有非常多的模板系 ...
- 怎样生成一个顶点迭代器(MItMeshVertex)
最近修改一个maya中的jlCollisionDeformer工具,该工具有一个明显不足,变形后顶点分布太乱,无法满足生产需求.于是考虑对该变形后的顶点进行平滑处理.既然要做平滑处理就要获取当前点及与 ...
- 简单分析下mybatis中mapper文件中小知识
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-// ...