文档章节

AQS 实现分析

o
 ovirtKg
发布于 2016/10/18 20:17
字数 895
阅读 35
收藏 2
AbstractQueuedSynchronizer

很多必发工具类 中是通过AQS实现的,比如说 ReentrantLock 内部 的公平锁和非公平锁。

 

如在LinkedBlockingQueue  阻塞队列中,使用了Reentrantlock 

ReentrantLock putLock = new ReentrantLock();  //内部初始化一个非公平的队列
Condition notFull = putLock.newCondition();

如put方法 

LinkedBlockingQueue  
public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    // Note: convention in all put/take/etc is to preset local var
    // holding count negative to indicate failure unless set.
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();   //取得锁的资源
    try {
        while (count.get() == capacity) {
            notFull.await();   //释放资源,等待条件唤醒
        }
        enqueue(node);  //加入到单向链表的tail
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();  //唤醒等待资源,还有资源
    } finally {
        putLock.unlock();   //释放锁资源
    }
    if (c == 0)
        signalNotEmpty();   //唤醒非空的条件线程,使得阻塞的take线程继续运行。
}

分析 如何获取锁的资源,接着 

ReentrantLock.  lockInterruptibly

ReentrantLock
public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

而sync就为AQS的子类,上面方法在AQS中已实现,

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))   //子类实现
        doAcquireInterruptibly(arg);
}

而对于非公平的锁,在tryAcquire中的实现中,调用了如下,直接尝试获取资源。而不同于公平的锁,要判断当前线程是否是内部维护的等待链表的头节点的next节点线程。要按照节点的顺序来获取资源。

ReentrantLock Sync
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;
}

失败后,继续进入 如下 获取方式

AQS
private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);  //加入双向链表的tail,如果为空,则new一个Node的标识为节点,内部数据为null,代表占用资源的线程。
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) { //判断前节点是否是占用资源的节点,而后尝试
                setHead(node);  //占用资源后,将当前节点置为head,head表示占用资源的节点。
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&   //前节点是否是singnal,不是则将前点cas为signal
                parkAndCheckInterrupt())    //阻塞,并检测是否中断
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

然而,即使前节点是head,但是head还没有释放资源时,判断pred的status是否是等待,如果不是且小于0,将pred有status置为singnal,等待信号,而后park, 等待unpark(release时 会将head的next unpark)。就是根据前节点状态决定是否阻塞。如果前继节点处于CANCELLED状态,则顺便删除这些节点重新构造队列。

 

锁的过程就介绍完了。

接下来解锁

ReentrantLock   unlock

ReentrantLock 
public void unlock() {
    sync.release(1);
}

也需将节点park,也就是将节点unpark

AQS的实现
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);  //唤醒后继节点
        return true;
    }
    return false;
}

其中当前线程释放资源,如果只lock过一次,释放成功。

ReentrantLock 
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;
}

唤醒后继节点后,在次获取。非公平的锁,唤醒后不一定成功,有可能被新加入的线程获得,从而导致刚被唤醒的线程再次被阻塞。

总结:

AQS中的tryAcquire 与tryRelease是 核心,根据不同的逻辑需求去实现  资源 的获取与释放。

参考

http://blog.csdn.net/chen77716/article/details/6641477

http://www.blogjava.net/zhanglongsr/articles/356782.html

http://www.infoq.com/cn/articles/java-blocking-queue/

http://www.cnblogs.com/paddix/p/5405678.html

 

© 著作权归作者所有

o
粉丝 3
博文 57
码字总数 48798
作品 0
景德镇
私信 提问
【JDK源码分析】深入源码分析CountDownLatch

前言 CountDownLatch是一个闭锁实现,它可以使一个或者多个线程等待一组事件发生。它包含一个计数器,用来表示需要等待的事件数量,coutDown方法用于表示一个事件发生,计数器随之递减,而a...

还是搬砖踏实
2018/07/26
0
0
阿里P7架构师带你深入分析AQS实现原理

内容导航 ReentrantLock重入锁的使用引入AQS 什么是AQS AQS的源码分析 简单解释一下J.U.C,是JDK中提供的并发工具包, java.util.concurrent。里面提供了很多并发编程中很常用的实用工具类,比...

Java-飞鱼
06/12
65
0
源码|并发一枝花之ReentrantLock与AQS(2):lockInterruptibly

上次分析了ReentrantLock#lock()与ReentrantLock#unlock()的实现原理,并初步讨论了AQS的等待队列模型,参考源码|并发一枝花之ReentrantLock与AQS(1):lock、unlock。 本文继续分析Reentra...

猴子007
2017/12/25
0
0
源码|并发一枝花之ReentrantLock与AQS(3):Condition

ReentrantLock#lock()、ReentrantLock#unlock()、ReentrantLock#lockInterruptibly()的分析见前文: 源码|并发一枝花之ReentrantLock与AQS(1):lock、unlock(这篇非常重要,解释了很多本文...

猴子007
2018/01/01
0
0
Java并发学习(三)-AbstractQueuedSynchronizer

花了一段时间学习了AbstractQueuedSynchronizer,研究了源码以及博客,这篇文章主要以自己的理解去一起学习AQS。 AQS简介 AQS是简写,全称是AbstractQueuedSynchronizer,在 包下面,这个类是...

anLA_
2017/11/23
0
0

没有更多内容

加载失败,请刷新页面

加载更多

RxJava进行单元测试的方式

@Test public void completeTask_retrievedTaskIsComplete() { // Given a new task in the persistent repository final Task newTask = new Task(TITLE, ""); ......

SuShine
33分钟前
5
0
正则表达式大全

检验手机号码 # 要求:手机号码必须为11位数字,以1开头,第二位为1或5或8。import redef verify_mobile(): mob = input("请输入手机号码:") ret = re.match(r"1[358]\d{9}", m......

彩色泡泡糖
37分钟前
5
0
QT之border-image属性

一、border-image的兼容性 border-image可以说是CSS3中的一员大将,将来一定会大放光彩,其应用潜力真的是非常的惊人。可惜目前支持的浏览器有限,仅Firefox3.5,chrome浏览器,Safari3+支持...

shzwork
37分钟前
6
0
Kubernetes Operator简易教程

1. 安装operator-sdk //安装 operator-sdk$ apt-get install operator-sdk.....$ operator-sdk versionoperator-sdk version: v0.7.0$ go versiongo version go1.11.4 darwin/amd64 2......

Robotcl_Blog
37分钟前
5
0
再谈DAG任务分解和Shuffle RDD

1、DagScheduler分析 DagScheduler功能主要是负责RDD的各个stage的分解和任务提交。Stage分解是从触发任务调度过程的finalStage开始倒推寻找父stage,如果父stage没有提交任务则循环提交缺失...

守望者之父
43分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部