一。AQS简介

  AQS(AbstractQueuedSynchronizer)抽象队列同步器,属于多线程编程的基本工具;JDK对其定义得很详细,并提供了多种常用的工具类(重入锁,读写锁,信号量,CyclicBarrier,CountDownLatch),在阅读源码的时候,我是从具体工具类往上读的,这样会比较便于理解AQS的设计。

  AQS的核心围绕着一个原子整型数state,通过对它增减控制是否当前线程获得锁,对其的操作使用了CAS的方法。而对于线程的排队机制,使用的是双向链表,每次进入排队的线程会链在最后,同样使用的是CAS添加末尾节点,使用了for(;;)的写法,完整名称为自旋CAS。

  下面,我将从五种常用类去分析源码,进而学习AQS。

  论文地址

二。开始吧,重入锁(ReetrantLock)

  重入锁的基本概念:它是一种可以将资源独占并且同一个线程可重新获得资源操作权限的锁对象,分为公平锁和非公平锁;公平锁的意义是,按照锁申请的顺序地获得操作权限,非公平锁的意义是,每个线程都是平等的,在每一次调用lock方法的时候均有可能竞争到锁,但如果未获取到锁则仍然会被投入排队队列中按顺序获取。

  

  我们要阅读的重入锁,它首先遵循Lock的规范,并且实现了序列化接口;而Lock的规范,必然定义了如何锁的,如何解锁的,并且规定了newCondition这个方法。而重入锁中,真正使用AQS的是他里面内涵的一个实现类Sync,它继承自AQS,并具有AQS的所有规范。

  这个内涵的Sync,在重入锁中实现了两种类型的队列,一个是公平队列,另一个是非公平队列,这取决于你构造重入锁的时候传入的是哪一个,默认是非公平锁;

  lock或者trylock这个方法,是进入锁对象逻辑的api,从此方法开始阅读,层层分析,就能明白AQS的机制了。lock方法是AQS里定义的一个抽象方法,被ReentrantLock中的Sync静态类所继承,而Sync类的两种实现:公平锁、非公平锁。重入锁对象的lock方法,实际上是调用内置的Sync对象的lock方法,而Sync的两种实现,在构造重入锁对象的时候指定。

    /**
* Acquires the lock.
*
* <p>Acquires the lock if it is not held by another thread and returns
* immediately, setting the lock hold count to one.
*
* <p>If the current thread already holds the lock then the hold
* count is incremented by one and the method returns immediately.
*
* <p>If the lock is held by another thread then the
* current thread becomes disabled for thread scheduling
* purposes and lies dormant until the lock has been acquired,
* at which time the lock hold count is set to one.
*/
public void lock() {
sync.lock();
}

  非公平锁申请,通过一个整型数acquires,使用CAS的方式设置AQS中的一个字段state,如果设置成功则将本锁对象的独占线程设置为currentThread;这个else if这里就是重入锁的意义,同一个线程到来的时候,会将state又加上acquires。所以多次调用lock方法的锁对象需要调用同等次数的unlock方法。

        /**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

  公平锁多了一个条件判断,就是hasQueuedPredecessors。就是判断是否为可获得资源的节点,具体为头结点的next节点。

        /**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

  这个方法里面有几种情况会返回false,(h != t)表示等待队列非空,为空就会返回false;另一种情况就是,队列非空,当前队列不等于后继结点的队列,会返回true。

    public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}

  这个方法是独占锁的核心操作,因为它的waiter是EXCLUSIVE。

    /**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

  这里就是核心的排队代码了,如果当前节点的上一个节点是头结点,也就是队列中的第二个节点,那么就有许可去申请资源了。申请失败,则进入线程park操作。

    /*
* Various flavors of acquire, varying in exclusive/shared and
* control modes. Each is mostly the same, but annoyingly
* different. Only a little bit of factoring is possible due to
* interactions of exception mechanics (including ensuring that we
* cancel if tryAcquire throws exception) and other control, at
* least not without hurting performance too much.
*/ /**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
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);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

  这里有个小地方,就是这个进队方法,如果头节点是空的,会new一个无意义的节点,这就是为什么前面说的第二个节点才是当前可以竞争资源的节点了。

    /**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
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;
}
}
}
}

  接着我们去看unlock方法,它指向的是AQS的release接口,与acquire相反,它是将state做减法;

  而这个方法,只有是独占线程调用才可以,因为所有lock的非独占线程,全都会被park;

    /**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
        protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}

  主要的唤醒操作在这个方法中执行,它将是传入的head节点的next节点,并且校验出这个得到的next节点是否是想要唤醒的节点。这里判断waitStatus小于0才取用是因为大于0的节点是cancel的节点,需要跳过。在插入队列的时候,是通过CAS设置tail节点的,所以从后往前肯定可以找到想要的节点。(比较难理解)

    /**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
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);
}

  

三。第二把锁,读写锁(ReetrantReadWriteLock)

  读写锁由如下几个成员变量组成,在申请读锁的时候是SHARE模式,申请写锁的时候是独占模式。

  

  他们使用同一个锁对象Sync,在创建对象的时候指定其为公平或者非公平的。读写锁重写了acquireShared方法,非常复杂不易读懂。

        /**
* Acquires the write lock.
*
* <p>Acquires the write lock if neither the read nor write lock
* are held by another thread
* and returns immediately, setting the write lock hold count to
* one.
*
* <p>If the current thread already holds the write lock then the
* hold count is incremented by one and the method returns
* immediately.
*
* <p>If the lock is held by another thread then the current
* thread becomes disabled for thread scheduling purposes and
* lies dormant until the write lock has been acquired, at which
* time the write lock hold count is set to one.
*/
public void lock() {
sync.acquire(1);
}
        /**
* Acquires the read lock.
*
* <p>Acquires the read lock if the write lock is not held by
* another thread and returns immediately.
*
* <p>If the write lock is held by another thread then
* the current thread becomes disabled for thread scheduling
* purposes and lies dormant until the read lock has been acquired.
*/
public void lock() {
sync.acquireShared(1);
}

四。共享锁,信号量(Semaphore)

  信号量是与重入锁完全不同类型的锁,因为他是共享的。它的作用用过的都有印象,就是多个线程可以共享这一把锁,而线程大于初始化凭证之后,就会被阻塞。

  

    /**
* Fair version
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L; FairSync(int permits) {
super(permits);
} protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}

  我相信可以很简单地找到这个FairSync,在semaphore中。它先判断是否只有你来申请,如果不是就回去操作state,如果申请小于0,直接返回,排队。

  

    /**
* Acquires in shared interruptible mode.
* @param arg the acquire argument
*/
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}

  与重入锁不同的是,入队的时候Node指定为SHARE模式,并且再次尝试获取锁,如果获取的锁是大于等于0的,将会调用setHeadAndPropagate方法传播释放。如果是大于0的,则会调用release接口,下一个唤醒的线程又会重复上述过程,一直唤醒到==0。和重复锁不一样的是,它具有传染性。

    /**
* Release action for shared mode -- signals successor and ensures
* propagation. (Note: For exclusive mode, release just amounts
* to calling unparkSuccessor of head if it needs signal.)
*/
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}

  只要调用了这个方法,就会进行上述所说的传染;正经的,这个方法就是激活线程并设置PROPAGATE,表示一直往后传播激活。

  共享锁和独占锁的区别在于,解锁线程会不会传染...

  这样,我们已经读了aqs里面的两种锁了。

五。工具,倒计时器(CountDownLatch)

  在看完上面的基本元素之后,搞一个倒计时器是什么鬼,不就是搞个变量然后一直减,然后等于0的时候一股脑儿释放???

   

    public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
} protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}

  很容易找到上述代码,我们调用倒计时器时候,就会调用countDown方法,每次都会给初始化减一。这时候主线程或者等待线程,调用await在等待。直到它减到0,就会做doReleaseShared,这个时候等待队列只有一个,就是父级线程,它就可以往下走了。因为这货减到0之后不会reset,所以不能复用。

AQS源码泛读,梳理设计流程(jdk8)的更多相关文章

  1. JDK1.8源码泛读之Arrays

    jdk1.8包含的常用集合工具类,一般包括两个: 数组工具类:`java.util.Arrays ` 结合工具类:`java.util.Collections` 今天就结合源码对`java.util. ...

  2. 硬核剖析Java锁底层AQS源码,深入理解底层架构设计

    我们常见的并发锁ReentrantLock.CountDownLatch.Semaphore.CyclicBarrier都是基于AQS实现的,所以说不懂AQS实现原理的,就不能说了解Java锁. 上篇 ...

  3. ibatis源码学习1_整体设计和核心流程

    背景介绍ibatis实现之前,先来看一段jdbc代码: Class.forName("com.mysql.jdbc.Driver"); String url = "jdb ...

  4. 并发-AQS源码分析

    AQS源码分析 参考: http://www.cnblogs.com/waterystone/p/4920797.html https://blog.csdn.net/fjse51/article/d ...

  5. AQS源码一窥-JUC系列

    AQS源码一窥 考虑到AQS的代码量较大,涉及信息量也较多,计划是先使用较常用的ReentrantLock使用代码对AQS源码进行一个分析,一窥内部实现,然后再全面分析完AQS,最后把以它为基础的同步 ...

  6. Cocos2dx源码赏析(1)之启动流程与主循环

    Cocos2dx源码赏析(1)之启动流程与主循环 我们知道Cocos2dx是一款开源的跨平台游戏引擎,而学习开源项目一个较实用的办法就是读源码.所谓,"源码之前,了无秘密".而笔者 ...

  7. (转)linux内存源码分析 - 内存回收(整体流程)

    http://www.cnblogs.com/tolimit/p/5435068.html------------linux内存源码分析 - 内存回收(整体流程) 概述 当linux系统内存压力就大时 ...

  8. AQS源码详细解读

    AQS源码详细解读 目录 AQS源码详细解读 基础 CAS相关知识 通过标识位进行线程挂起的并发编程范式 MPSC队列的实现技巧 代码讲解 独占模式 独占模式下请求资源 独占模式下的释放资源 共享模式 ...

  9. AQS源码深入分析之共享模式-你知道为什么AQS中要有PROPAGATE这个状态吗?

    本文基于JDK-8u261源码分析 本篇文章为AQS系列文的第二篇,前文请看:[传送门] 第一篇:AQS源码深入分析之独占模式-ReentrantLock锁特性详解 1 Semaphore概览 共享模 ...

随机推荐

  1. maven使用感受

    第一次接触的时候,什么都不懂,感觉好复杂. 后来系统地看了一个使用教程: 简单评价一下: 自动帮我们下载jar架包,还有就是可以执行命令自己部署到远程服务器上面去. 缺点: 学习成本.一般人不了解.第 ...

  2. Python鸭子类型思想

    动态语言中经常提到鸭子类型,所谓鸭子类型就是:如果走起路来像鸭子,叫起来也像鸭子,那么它就是鸭子(If it walks like a duck and quacks like a duck, it ...

  3. 《Just for Fun》---读后感

    <Just for Fun>本书是Linux之父林纳斯自传,书名的意思是:只是为了好玩.主要是讲了林纳斯的人生经历,以及Linux的诞生过程.Linux从一个终端仿真器到一个世界瞩目的操作 ...

  4. Spring Boot 集成 Mybatis(druid 数据库连接池 以及 分页配置)

    MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射,目前很大一部分互联网.软件公司都在使用这套框架 关于Mybatis-Generator的下载可以到这个地址:http ...

  5. python类的继承-1

    #!/usr/bin/python3 #类定义 class people: #定义基本属性 name = '' age = 0 #定义私有属性,私有属性在类外部无法直接进行访问 __weight = ...

  6. UVa 111 History Grading (简单DP,LIS或LCS)

    题意:题意就是坑,看不大懂么,结果就做不对,如果看懂了就so easy了,给定n个事件,注意的是, 它给的是第i个事件发生在第多少位,并不是像我们想的,第i位是哪个事件,举个例子吧,4 2 3 1, ...

  7. Hdu4632 Palindrome subsequence 2017-01-16 11:14 51人阅读 评论(0) 收藏

    Palindrome subsequence Problem Description In mathematics, a subsequence is a sequence that can be d ...

  8. linux环境下(非UI操作)所有软件的安装与卸载总结

    UI界面的软件管理 linux下的软件一般都是经过压缩的,主要的格式有这几种:rpm.tar.tar.gz.tgz等.所以首先拿到软件后第一件事就是解压缩. 在xwindow下以rpm格式的软件安装比 ...

  9. hdu 4982 贪心构造序列

    http://acm.hdu.edu.cn/showproblem.php?pid=4982 给定n和k,求一个包含k个不相同正整数的集合,要求元素之和为n,并且其中k-1的元素的和为完全平方数 枚举 ...

  10. hdu 5036 概率+bitset

    http://acm.hdu.edu.cn/showproblem.php?pid=5036 n个房间每个房间里面有一把或多把钥匙可以打开其他的门.如果手上没有钥匙可以选择等概率随机选择一个门炸开,求 ...