要理解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源码解析的更多相关文章

  1. 并发编程之:AQS源码解析

    大家好,我是小黑,一个在互联网苟且偷生的农民工. 在Java并发编程中,经常会用到锁,除了Synchronized这个JDK关键字以外,还有Lock接口下面的各种锁实现,如重入锁ReentrantLo ...

  2. 浩哥解析MyBatis源码(十一)——Parsing解析模块之通用标记解析器(GenericTokenParser)与标记处理器(TokenHandler)

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6724223.html 1.回顾 上面的几篇解析了类型模块,在MyBatis中类型模块包含的 ...

  3. JAVA常用集合源码解析系列-ArrayList源码解析(基于JDK8)

    文章系作者原创,如有转载请注明出处,如有雷同,那就雷同吧~(who care!) 一.写在前面 这是源码分析计划的第一篇,博主准备把一些常用的集合源码过一遍,比如:ArrayList.HashMap及 ...

  4. Spring源码情操陶冶-自定义节点的解析

    本文承接前文Spring源码情操陶冶-DefaultBeanDefinitionDocumentReader#parseBeanDefinitions,特开辟出一块新地来啃啃这块有意思的骨头 自定义节 ...

  5. Spring源码情操陶冶-ComponentScanBeanDefinitionParser文件扫描解析器

    承接前文Spring源码情操陶冶-自定义节点的解析,本文讲述spring通过context:component-scan节点干了什么事 ComponentScanBeanDefinitionParse ...

  6. Spring源码情操陶冶-AnnotationConfigBeanDefinitionParser注解配置解析器

    本文承接前文Spring源码情操陶冶-自定义节点的解析,分析spring中的context:annotation-config节点如何被解析 源码概览 对BeanDefinitionParser接口的 ...

  7. Spring源码情操陶冶-PropertyPlaceholderBeanDefinitionParser注解配置解析器

    本文针对spring配置的context:property-placeholder作下简单的分析,承接前文Spring源码情操陶冶-自定义节点的解析 spring配置文件应用 <context: ...

  8. SpringMVC源码情操陶冶-InterceptorsBeanDefinitionParser拦截器解析器

    解析mvc:interceptors节点 观察下InterceptorsBeanDefinitionParser的源码备注 /** * {@link org.springframework.beans ...

  9. JDK源码及其他框架源码解析随笔地址导航

    置顶一篇文章,主要是整理一下写过的JDK中各个类的源码及其他框架源码解析的文章,方便自己随时阅读也方便网友朋友们阅读与指正 基础篇 从为什么String=String谈到StringBuilder和S ...

随机推荐

  1. 3.3 unittest批量执行

    3.3 unittest批量执行 我们在写用例的时候,单个脚本的用例好执行,那么多个脚本的时候,如何批量执行呢?这时候就需要用到unittet里面的discover方法来加载用例了.加载用例后,用un ...

  2. indexOf 引用

  3. Centos7 安装Nginx 防火墙开放80端口给外网访问

    Centos7的防火墙改成了firewall,不再叫iptables,开放端口的方法如下: firewall-cmd --zone=public --add-port=80/tcp --permane ...

  4. web-msg-send 学习 http://www.workerman.net/web-sender

    WEB消息推送框架 web-msg-sender是一款web长连接推送框架,采用PHPSocket.IO开发,基于WebSocket长连接通讯,如果浏览器不支持WebSocket则自动转用comet推 ...

  5. 《DSP using MATLAB》Problem 7.9

    代码: %% ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ %% Output In ...

  6. $\mathcal{OI}$生涯中的各种数论算法的证明

    嗯,写这个是因为我太弱了\(ORZ\). #\(\mathcal{\color{silver}{1 \ \ Linear \ \ Sieve \ \ Method \ \ of \ \ Prime}} ...

  7. 通过用户名&密码验证访问远程共享文件夹 C#

    通过代码先在cmd中运行net use进行验证,然后就可访问共享文件了. 验证方法如下: public string connectState(string path/*要访问的文件路径*/, str ...

  8. <a>标签里的函数事件写法的实战建议

    如果在实际应用中确实是要用到标签来响应onclick事件的, 那么就建议使用下面三种方法 <a href="javascript:void(0);" onclick=&quo ...

  9. springmvc log4j配置

    1. web.xml <!-- 加载Log4J 配置文件 --> <context-param> <param-name>log4jConfigLocation&l ...

  10. Ubuntu下三种方法设置环境变量

    一种用于当前终端,一种用于当前用户,一种用于所有用户: 用于当前终端: 在当前终端中输入:export PATH=$PATH:<你的要加入的路径> 不过上面的方法只适用于当前终端,一旦当前 ...