JVM中显示锁基础AbstractQueuedSynchronizer
在研究AbstractQueuedSynchronizer的时候我是以ReentrantLock入手的。所以理所当然会设计到一些ReentrantLock的方法。因为网上已经有很多关于AQS的文章了,所以这篇文章不会特别详细的去记录类的实现,主要是记录几个我觉得需要主要的点。
1、阻塞队列实现
AbstractQueuedSynchronizer用一个Node队列来实现线程阻塞。处理当前正在执行的线程,后续的所有的线程都会进入到这个虚拟的CLH队列。下面该图是我根据源码画的一个链表队列。head是一个空对象,也就是这个对象是没有thread的,后续的thread都会添加到tail。进入到这个队列的thread都会被操作系统挂起,等正在执行的thread被释放后操作系统会唤醒被阻塞的head的next节点的线程,具体的唤醒方法在unparkSuccess函数,下面会有所分析。

代码的实现思路图解
为了方便理解,我粗略的用word画了一个代码流程图,包含lock和unlock方法。

锁的实现分析
当我们跟踪lock代码的时候,在队列第一次创建的时候会执行这个enq函数:
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
仔细看下这个函数会发现,第一个节点是默认给的,在代码第5行,也就是一个空节点new Node()。然后接下来就是把新建的节点插入到tail。
在进入队列后,还没被阻塞之前,会进行一次判断,判断当前node的prev节点是不是head。为什么不直接判断当前节点是不是head,以上的图片已经说明清楚了,head节点的thread是null的,也就是head是默认生成的。为什么要这样做?这样做的好处是什么我还暂时还没想到。如果有网友知道麻烦留言告诉我下哈。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();//1
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
但是在上面注释1中获取当前node的前置节点。如果p是head的话,那么node的线程会尝试获取一次锁tryAcquire。
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
如果还是获取不到锁,那么久当前线程阻塞。实现方法是调用 LockSupport.park,改方法会直接调用操作系统的内置方法来实现线程阻塞:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
2、线程唤醒
当依噶线程unlock后就会释放锁,那么之前争夺锁的线程都被阻塞挂起了,现在要做的一件事情就是唤醒挂起的线程。当然不是把所有的线程都唤醒,那么它的唤醒规则是怎么样的呢?显然因为阻塞锁是一个FIFO的队列,所以肯定是从head开始。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
开始的时候我们已经说过head是空的,所以唤醒要从第二个节点开始,看下面Node s = node.next;就知。
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)
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) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
3、Node几点状态码
把线程要包装为Node对象的主要原因,除了用Node构造供虚拟队列外,还用Node包装了各种线程状态,这些状态被精心设计为一些数字值:
SIGNAL(-1) :线程的后继线程正/已被阻塞,当该线程release或cancel时要重新这个后继线程(unpark)
CANCELLED(1):因为超时或中断,该线程已经被取消
CONDITION(-2):表明该线程被处于条件队列,就是因为调用了Condition.await而被阻塞
PROPAGATE(-3):传播共享锁
0:0代表无状态
参考:
http://www.open-open.com/lib/view/open1352431606912.html
JVM中显示锁基础AbstractQueuedSynchronizer的更多相关文章
- java中的锁之AbstractQueuedSynchronizer源码分析(一)
一.AbstractQueuedSynchronizer类介绍. 该抽象类有两个内部类,分别是静态不可继承的Node类和公有的ConditionObject类.AbstractQueuedSynchr ...
- java中的锁之AbstractQueuedSynchronizer源码分析(二)
一.成员变量. 1.目录. 2.state.该变量标记为volatile,说明该变量是对所有线程可见的.作用在于每个线程改变该值,都会马上让其他线程可见,在CAS(可见锁概念与锁优化)的时候是必不可少 ...
- Java中的显示锁 ReentrantLock 和 ReentrantReadWriteLock
在Java1.5中引入了两种显示锁,分别是可重入锁ReentrantLock和可重入读写锁ReentrantReadWriteLock.它们分别实现接口Lock和ReadWriteLock.(注意:s ...
- Synchronized 和 Lock 锁在JVM中的实现原理以及代码解析
一.深入JVM锁机制:synchronized synrhronized关键字简洁.清晰.语义明确,因此即使有了Lock接口,使用的还是非常广泛.其应用层的语义是可以把任何一个非null对象作为&qu ...
- {Django基础六之ORM中的锁和事务}一 锁 二 事务
Django基础六之ORM中的锁和事务 本节目录 一 锁 二 事务 一 锁 行级锁 select_for_update(nowait=False, skip_locked=False) #注意必须用在 ...
- day 71 Django基础六之ORM中的锁和事务
Django基础六之ORM中的锁和事务 本节目录 一 锁 二 事务 三 xxx 四 xxx 五 xxx 六 xxx 七 xxx 八 xxx 一 锁 行级锁 select_for_update(no ...
- day 58 Django基础六之ORM中的锁和事务
Django基础六之ORM中的锁和事务 本节目录 一 锁 二 事务 三 xxx 四 xxx 五 xxx 六 xxx 七 xxx 八 xxx 一 锁 行级锁 select_for_update( ...
- Java并发(基础知识)——显示锁和同步工具类
显示锁 Lock接口是Java ...
- 探究Java中的锁
一.锁的作用和比较 1.Lock接口及其类图 Lock接口:是Java提供的用来控制多个线程访问共享资源的方式. ReentrantLock:Lock的实现类,提供了可重入的加锁语义 ReadWrit ...
随机推荐
- 谈"自驱力"
最新说明: 1.标题是为了博眼球取的,请不大家不要纠结具体薪资数字,我瞎取的 2.请注意素质,不要满口喷粪,不要搞人身攻击,尊重别人,就是尊重你自己 3.请大家就事论事,不要胡乱臆想,请站在全局的角度 ...
- RPM 包下载 GCC 4.8安装
http://ftp.scientificlinux.org/linux/scientific/ http://www.rpmfind.net/linux/rpm2html/search.php?qu ...
- 泊松回归(Poisson Regression)
本博客已经迁往http://www.kemaswill.com/, 博客园这边也会继续更新, 欢迎关注~ Linear Regression预测的目标\(Y\)是连续值, Logistic Regre ...
- linux硬链接和软链接的区别
1.原理上: 硬链接(hard link):A是B的硬链接(A和B都是文件名),则A的目录项中的inode节点号与B的目录项中的inode节点号相同,即一个inode节点对应两个不同的文件名,两个文件 ...
- python 字符串替换
字符串替换可以用内置的方法和正则表达式完成.1用字符串本身的replace方法: a = 'hello word'b = a.replace('word','python')print b 2用正则表 ...
- 读书笔记_Effective_C++_条款四十四:将与参数无关的代码抽离template
标题上说“将与参数无关的代码抽离template”,这里的参数既可以指类型,也可以是非类型,我们先来看看非类型的情况. 假定我们要为矩阵写一个类,这个矩阵的行列元素个数相等,是一个方阵,因而我们可以对 ...
- Haskell 与范畴论
说到 Haskell,这真是一门逼格极高的编程语言,一般初学者如果没有相关函数式编程的经验,入门直接接触那些稀奇古怪的概念,简直要跪下.现在回想起来,隐隐觉得初学者所拥有的命令式编程语言(impera ...
- 文件比对工具(Beyond Compare)
文件比对工具: 工具名称:Beyond Compare 版本号:v3.3.13 下载地址:http://i.cnblogs.com/Files.aspx 官网最新版本下载地址:http://www.s ...
- udp穿透简单讲解和实现(Java)
在上一小节中了解到了通过浏览器自带的Webrtc功能来实现P2P视频聊天.在HTML5还没有普及和制定Webrtc标准的前提下,如果要在手机里进行视频实时对话等包括其他功能的话,还是要自己实现,还比较 ...
- ruby self.included用法
===Module#included 当一个模块混入到类时,如果该模块的included方法已经定义,那么该方法被调用.该方法的唯一参数就是接受混含的类的名字. module M def self.i ...