文档章节

java并发-AQS总结-原理

萧默
 萧默
发布于 2019/12/14 16:12
字数 2365
阅读 7
收藏 0

AQS是Java多线程编程的重入锁,管程,工具类的基础类,是必须要掌握的。不掌握这个类,根本不能称之为合格的Java程序员。

即使是把这个类所有的代码都背会,也是值得的。

如何标识已经有线程在执行呢?

有两个变量,一个state变量,一个exclusiveOwnerThread变量,为什么需要两个变量呢?用一个exclusiveOwnerThread变量不也可以吗?

state变量用来支持重入,exclusiveOwnerThread变量用来支持互斥。

state变量标识是否已经有线程获得锁。但是这里为什么只是用cas尝试设置一次呢?如下代码所示:

复制代码 static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L;

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

复制代码 如果说这里使用CAS尝试修改无限循环的设置,那么能够就能简单的实现锁了呢?如下代码所示:

final void lock() { int state = getState(); while (!compareAndSetState(state, 1)){

        }
    }        

这里使用CAS设置state成员变量明显不行,那能够设置当前线程呢?这里是不行的,CAS只能保证多个线程并发修改数据的线程安全特性,只有一个线程修改成功,但无法保证这类数据被一个线程锁占用时,另一个线程的CAS操作会失败的情况!!!所以,这里只能尝试设置一次状态,这里判断的用意是看当前线程执行到这里,有没有其他的线程已经设置过状态,若设置过,那么就只能加入到队列,否则,自己就直接加锁不就得了。

acquire方法为何最后要自我打断?

分析情况应该分多钟情况,这样比较好分析。

情景1:节点类型

1.节点全部都是独占模式的情景模拟

2.节点全部都是共享模式的情景模拟

3.节点既有独占模式,又有共享模式的情景模拟。

情景2:

1.当有一个线程加入队列时,此时持有锁的线程释放。。。

2.当有2个线程加入队列时,此时持有锁的线程释放。。。

为什么会同时存在addWaiter方法和enq方法呢?只使用addWatier方法不就行了吗?

addWatier方法适用于只有一个线程加入队列,此时没有竞争,很快就能加入队列。

enq方法同时考虑了两种情况:

1.多个线程都要加入队列

此时为了提高效率,先尝试一次CAS设置,至此有一个线程成功设置,让其他线程后进入enq方法,让所有要加入队列的线程自旋CAS加入队列。

2.队列为空

此时需要初始化一个头结点,而且,为了只能设置一个头结点,运用CAS来操作。

复制代码 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; } } } } 复制代码 这里比较经典,只有一个线程会走Must initialize,而且只会发生一次!!!然后,大家都会走下面的分支来依次添加到队尾。思考,这个Head什么都没有,如何处置他呢?

我感觉下面会竞争执行权。

结点状态深入理解!!!结点有好几种状态,必须理解一下。

节点状态有:

1.被取消

-1 等待通知

-2 等待条件

-3 ???

什么时候会出现被取消的节点呢?

当调用tryLock(TimeUinit)这样的方法时,如果尝试获得锁失败,则会将节点变成取消状态。

什么时候会出现取消的节点呢?

1.当超过等待时间依然没有获得锁,则退出方法时,会设置成取消状态

2.当等待过程过,出现打断异常,则退出方法时,会设置取消状态

为什么会保留这样的取消的节点呢?

因为如果要线程自己移除这个取消的节点,还是要保证线程安全,花费的时间是不确定的,所以,不如简单的设置一下已取消状态,让其他的线程帮自己移除,而自己尽快返回,不浪费时间。

使用AQS组件的锁的线程什么时候会自旋?

感觉没有地方会自旋。

为什么要用双向链表存储被阻塞的线程,而不是使用单链表?

1.节省寻找前驱节点的时间

为何ConditionObject是使用单链表,而不是双向链表呢?

够用就好

ReentantLock是否是公平锁?如果是,如何实现公平锁的?

通过一个boolean类型参数的构造函数来决定是否公平,公平锁保证等待的所有线程都有平等的机会获执行权,实现方式就是有一个线程获得锁之后,在这个线程没有获得锁之前,其他线程一直自旋等待加入队列,公平锁比较浪费CPU计算资源。此时等待队列就相当于退化到了只有一个节点的队列。。。。

非公平锁,是先来先服务的锁,谁先能够加入的等待队列,则谁就先被通知执行,非公平锁是阻塞锁,相较于公平锁,节省CPU计算资源。

针对这个类的每个方法,都仔细思考思考,切实理解每个方法的实现思路和原因?为什么要这样做比这个方法做了什么更重要。

每个方法的原因###############

释放互斥锁的操作

release方法,内部调用tryRelease方法和unpakcSuccessor(唤醒后继节点)方法。

tryRelease方法返回true说明完全释放了锁,所以其他等待的线程将会调用acquire方法获得锁。(其实是正在等待和正在调用acquire方法的线程)

unpackSuccessor方法唤醒后继节点,这里比较的有意思。

这个方法先找到第一个,是从双链表的尾部项头部遍历,找到哨兵节点的后继节点,然后unpack操作。

思考,谁把当前的这个节点从链表当中移除的呢?因为此时unpack操作,一定唤醒的是在睡眠的线程,所以,应该是要看做pack操作的后面的代码,因为此时会从这里被唤醒然后继续执行!!!

从acquireQueued方法可以看出,是线程自己把自己设置为头结点???哨兵节点不是头结点吗?

自己设置为头结点之后,就放弃了成为等待节点的能力,因为setHead方法会设置必要的字段为空,这样就成为哨兵节点了。此时,是放弃了之前的哨兵节点了!!!

获得互斥锁的操作

可中断的获得互斥锁的操作

超时获得互斥锁的操作

尝试一次获得互斥锁的操作

注意:哨兵节点的加入,其实也是惰性的,只有当有线程需要加入阻塞队列的时候才会创建哨兵节点,

哨兵节点来自3个操作:

1.第一个要加入阻塞队列的线程追加的

2.当获得互斥锁的线程要释放互斥锁,那么就会unpack后继节点,后继节点线程醒来,就会设置自己所在的节点为哨兵节点,那么,此时,哨兵节点的status字段就标识了当前线程之前所处的状态了。

3.释放共享锁,跟释放互斥锁类似。

#######################################################################################

释放共享锁的操作

可中断获得共享锁的操作

获得共享锁的操作

超时获得共享锁的操作

尝试一次获得互斥锁的操作

##########condition对象

为什么condition对象的wait和single类方法使用之前需要加锁,使用之后需要解锁?

因为这类方法都不是线程安全的方法,需要借助加锁来保证线程安全,保证自己追加Waiter能够线程安全。

包括增加等待节点,取消等待节点。

这里不是线程安全的方法,正好可以借助ReentantLock来保证线程安全。这样做,简化了Condition接口实现类的写法,比较容易理解。

conditionObject是condition的实现类,作为一个内部类,用意是能够访问外部类对象AQS的一些属性。

condition对象的本质依然是对等待对列的操作,两种操作,入队,出队,提供等待和通知两种比较实用的方法的工具组件。

可以实现管程。

condition对象的singleAll是一个一个通知的。先来先通知。

condition构建自己的等待队列不久OK了吗?为何还要让描述自己的节点加入AQS的同步队列呢?

© 著作权归作者所有

萧默
粉丝 0
博文 43
码字总数 19380
作品 0
杭州
程序员
私信 提问
【Java并发专题】27篇文章详细总结Java并发基础知识

努力的意义,就是,在以后的日子里,放眼望去全是自己喜欢的人和事! github:https://github.com/CL0610/Java-concurrency,欢迎题issue和Pull request。所有的文档都是自己亲自码的,如果觉...

你听___
2018/05/06
0
0
CountDownLatch源码解析

CountDownLatch 相比ReentranceLock,CountDownLatch的流程还是相对比较简单的,CountDownLatch也是基于AQS,它是AQS的共享功能的一个实现。 下面从源代码的实现上详解CountDownLatch。 1、C...

maxam0128
2018/01/23
0
0
Java 并发编程源码解析汇总篇

java并发编程,内存模型 java并发编程,volatile内存实现和原理 Java并发编程,并发基础 Java 并发编程,线程池(ThreadPoolExecutor)源码解析 Java并发编程,Executor 框架介绍 Java并发编...

郑加威
2018/12/23
0
0
Java 并发编程-不懂原理多吃亏(送书福利)

作者 加多 关注阿里巴巴云原生公众号,后台回复关键字“并发”,即可参与送书抽奖!** 导读:并发编程与 Java 中其他知识点相比较而言学习门槛较高,从而导致很多人望而却步。但无论是职场面...

阿里巴巴云原生
2019/08/30
113
0
【死磕Java并发】—– 死磕 Java 并发精品合集

【死磕 Java 并发】系列是 LZ 在 2017 年写的第一个死磕系列,一直没有做一个合集,这篇博客则是将整个系列做一个概览。 先来一个总览图: 【高清图,请关注“Java技术驿站”公众号,回复:脑...

chenssy
2018/07/22
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Kettle自定义jar包供javascript使用

我们都知道 Kettle 是用 Java 语言开发,并且可以在 JavaScript 里面直接调用 java 类方法。所以有些时候,我们可以自定义一些方法,来供 JavaScript 使用。 本篇文章有参考自:https://www...

CREATE_17
昨天
102
0
处理CSV文件中的逗号

我正在寻找有关如何处理正在创建的csv文件的建议,然后由我们的客户上传,并且该值可能带有逗号(例如公司名称)。 我们正在研究的一些想法是:带引号的标识符(值“,”值“,”等)或使用|...

javail
昨天
79
0
如何克隆一个Date对象?

将Date变量分配给另一个变量会将引用复制到同一实例。 这意味着更改一个将更改另一个。 如何实际克隆或复制Date实例? #1楼 简化版: Date.prototype.clone = function () { return new ...

技术盛宴
昨天
73
0
计算一个数的数位之和

计算一个数的数位之和 例如:128 :1+2+8 = 11 public int numSum(int num) { int sum = 0; do { sum += num % 10; } while ((num = num / 10) > 0); return sum;......

SongAlone
昨天
124
0
为什么图片反复压缩后普遍会变绿,而不是其他颜色?

作者:Lion Yang 链接:https://www.zhihu.com/question/29355920/answer/119088684 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 业余版概要:安卓的...

shzwork
昨天
81
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部