在研究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的更多相关文章

  1. java中的锁之AbstractQueuedSynchronizer源码分析(一)

    一.AbstractQueuedSynchronizer类介绍. 该抽象类有两个内部类,分别是静态不可继承的Node类和公有的ConditionObject类.AbstractQueuedSynchr ...

  2. java中的锁之AbstractQueuedSynchronizer源码分析(二)

    一.成员变量. 1.目录. 2.state.该变量标记为volatile,说明该变量是对所有线程可见的.作用在于每个线程改变该值,都会马上让其他线程可见,在CAS(可见锁概念与锁优化)的时候是必不可少 ...

  3. Java中的显示锁 ReentrantLock 和 ReentrantReadWriteLock

    在Java1.5中引入了两种显示锁,分别是可重入锁ReentrantLock和可重入读写锁ReentrantReadWriteLock.它们分别实现接口Lock和ReadWriteLock.(注意:s ...

  4. Synchronized 和 Lock 锁在JVM中的实现原理以及代码解析

    一.深入JVM锁机制:synchronized synrhronized关键字简洁.清晰.语义明确,因此即使有了Lock接口,使用的还是非常广泛.其应用层的语义是可以把任何一个非null对象作为&qu ...

  5. {Django基础六之ORM中的锁和事务}一 锁 二 事务

    Django基础六之ORM中的锁和事务 本节目录 一 锁 二 事务 一 锁 行级锁 select_for_update(nowait=False, skip_locked=False) #注意必须用在 ...

  6. day 71 Django基础六之ORM中的锁和事务

    Django基础六之ORM中的锁和事务   本节目录 一 锁 二 事务 三 xxx 四 xxx 五 xxx 六 xxx 七 xxx 八 xxx 一 锁 行级锁 select_for_update(no ...

  7. day 58 Django基础六之ORM中的锁和事务

      Django基础六之ORM中的锁和事务   本节目录 一 锁 二 事务 三 xxx 四 xxx 五 xxx 六 xxx 七 xxx 八 xxx 一 锁 行级锁 select_for_update( ...

  8. Java并发(基础知识)——显示锁和同步工具类

    显示锁                                                                                     Lock接口是Java ...

  9. 探究Java中的锁

    一.锁的作用和比较 1.Lock接口及其类图 Lock接口:是Java提供的用来控制多个线程访问共享资源的方式. ReentrantLock:Lock的实现类,提供了可重入的加锁语义 ReadWrit ...

随机推荐

  1. [ITSEC]信息安全·Web安全培训第一期客户端安全之UBB系列

    缩略图: 引文: 所谓UBB代码,是指论坛中的替代HTML代码的安全代码.ubb发帖编辑器 这种代码使用正则表达式来进行匹配,不同的论坛所使用的UBB代码很可能不同,不能一概而论.UBB代码的出现,使 ...

  2. C# MailMessage Attachment 中文名附件发邮件-Firefox中文显示正常,网页打开邮件附件中文名乱码

    一.故事 首先通过CDO.Message来获取邮件EML相关数据:邮件标题.邮件内容.邮件附件.发件人.收件人.CC主要就这么几个,其次通过MailMessage来组织邮件通过Python来发送邮件! ...

  3. C用函数指针模拟重载 C++重载

    C中为什么不支持重载,即同一作用域内不允许出现同名函数? 我们都知道重载是c++面向对象的特性.c语言中是不存在的.所谓重载简单来说就是一个函数名可以实现不同的功能,要么输入参数不同或者参数个数不同, ...

  4. MyBatis+MySQL 返回插入的主键ID

    需求:使用MyBatis往MySQL数据库中插入一条记录后,需要返回该条记录的自增主键值. 方法:在mapper中指定keyProperty属性,示例如下: <insert id="i ...

  5. 通过DOS、SHELL批处理命令加载Lib并编译和打包Java项目(或者运行项目)

    有些时候,需要通过DOS批处理来编译整个项目的JAVA文件:并且编译后还要对Class文件进行打包成jar文件...这还不是最烦的,最烦的是,编译和打包的时候需要依赖其他多个jar文件,困难就这么来了 ...

  6. MAC自带的SVN进行升级

    1.下载高版本svn:http://www.wandisco.com/subversion/download 2.安装 3. #1.在.bash_profile添加export PATH=/opt/s ...

  7. C++ 检测内存泄露工具 -- Windows平台

    平台:Windows7 64bit,编译器G++(mingw) 工具:Dr Memory,项目主页:https://code.google.com/p/drmemory/ (可能要FQ,可能会很慢,所 ...

  8. linux下mysql字符集编码问题的修改

    安装完的MySQL的默认字符集为 latin1 ,为了要将其字符集改为用户所需要的(比如utf8),就必须改其相关的配置文件:由于linux下MySQL的默认安装目录分布在不同的文件下:不像windo ...

  9. SNF开发平台WinForm之一-开发-单表表格编辑管理页面-SNF快速开发平台3.3-Spring.Net.Framework

    1.1运行效果: 1.2开发实现: 1.2.1          首先在数据库中创建需要开发的数据表,在代码生成器中进行配置连接数据库. 代码生成器的Config.xml文件配置如下节点: 1.2.2 ...

  10. andorid jni入门教程一之helloworld

    开发环境:windows2007, eclipse 做anroid越深发现用到底层开发的时候越多,但是我以前也没有搞过,因此现在打算好好学习学习.先从最简单的做起.正所谓万事开头难啊. 搞了近一天终于 ...