概述:

AbstractQueuedSynchronizer,可以称为抽象队列同步器。

AQS有独占模式和共享模式两种:

  • 独占模式:

公平锁:

非公平锁:

  • 共享模式:

数据结构:

  • 基本属性:
/**
* 同步等待队列的头结点
*/
private transient volatile Node head; /**
* 同步等待队列的尾结点
*/
private transient volatile Node tail; /**
* 同步资源状态
*/
private volatile int state;
  • 内部类:
static final class Node {
/**
* 标记节点为共享模式
*/
static final Node SHARED = new Node();
/**
* 标记节点为独占模式
*/
static final Node EXCLUSIVE = null; static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3; /**
* CANCELLED: 值为1,表示当前的线程被取消
* SIGNAL: 值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
* CONDITION: 值为-2,表示当前节点在等待condition,也就是在condition队列中;
* PROPAGATE: 值为-3,表示当前场景下后续的acquireShared能够得以执行;
* 0: 表示当前节点在sync队列中,等待着获取锁。
* 表示当前节点的状态值
*/
volatile int waitStatus; /**
* 前置节点
*/
volatile Node prev; /**
* 后继节点
*/
volatile Node next; /**
* 节点同步状态的线程
*/
volatile Thread thread; /**
* 存储condition队列中的后继节点
*/
Node nextWaiter; /**
* 是否为共享模式
*/
final boolean isShared() {
return nextWaiter == SHARED;
} /**
* 获取前驱结点
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
} Node() { // Used to establish initial head or SHARED marker
} Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
} Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}

主要方法解析:

  • tryAcquire/tryAcquireShared(int arg)

  独占/共享模式获取锁;由子类实现,仅仅获取锁,获取锁失败时不进行阻塞排队。

  • tryRelease/tryReleaseShared(int arg)

  独占/共享模式释放锁;由子类实现,仅仅释放锁,释放锁成功不对后继节点进行唤醒操作。

  • acquire/acquireShared(int arg)

  独占/共享模式获取锁,如果线程被中断唤醒,会返回线程中断状态,不会抛异常中止执行操作(忽略中断)。

  • acquireInterruptibly/acquireSharedInterruptibly(int arg)

  独占/共享模式获取锁,线程如果被中断唤醒,则抛出InterruptedException异常(中断即中止)。

  • tryAcquireNanos/tryAcquireSharedNanos(int arg, long nanosTimeout)

  独占/共享时间中断模式获取锁,线程如果被中断唤醒,则抛出InterruptedException异常(中断即中止);如果超出等待时间则返回加锁失败。

  • release/releaseShared(int arg)

  独占/共享模式释放锁。

  • addWaiter(Node mode)

  将给定模式节点进行入队操作。

 1     private Node addWaiter(Node mode) {
2 // 根据指定模式,新建一个当前节点的对象
3 Node node = new Node(Thread.currentThread(), mode);
4 // Try the fast path of enq; backup to full enq on failure
5 Node pred = tail;
6 if (pred != null) {
7 // 将当前节点的前置节点指向之前的尾结点
8 node.prev = pred;
9 // 将当前等待的节点设置为尾结点(原子操作)
10 if (compareAndSetTail(pred, node)) {
11 // 之前尾结点的后继节点设置为当前等待的节点
12 pred.next = node;
13 return node;
14 }
15 }
16 enq(node);
17 return node;
18 }
  • enq(final Node node)

  将节点设置为尾结点。注意这里会进行自旋操作,确保节点设置成功。因为等待的线程需要被唤醒操作;如果操作失败,当前节点没有与其他节点没有引用指向关系,一直就不会被唤醒(除非程序代码中断线程)。

 1     private Node enq(final Node node) {
2 for (;;) {
3 Node t = tail;
4 // 判断尾结点是否为空,尾结点初始值是为空
5 if (t == null) { // Must initialize
6 // 尾结点为空,需要初始化
7 if (compareAndSetHead(new Node()))
8 tail = head;
9 } else {
10 // 设置当前节点设置为尾结点
11 node.prev = t;
12 if (compareAndSetTail(t, node)) {
13 t.next = node;
14 return t;
15 }
16 }
17 }
18 }
  • acquireQueued(final Node node, int arg)

  已经在队列当中的节点,准备阻塞获取锁。在阻塞前会判断前置节点是否为头结点,如果为头结点;这时会尝试获取下锁(因为这时头结点有可能会释放锁)。

 1     final boolean acquireQueued(final Node node, int arg) {
2 boolean failed = true;
3 try {
4 boolean interrupted = false;
5 for (;;) {
6 // 当前节点的前置节点
7 final Node p = node.predecessor();
8 // 入队前会先判断下该节点的前置节点是否是头节点(此时头结点有可能会释放锁);然后尝试去抢锁
9 // 在非公平锁场景下有可能会抢锁失败,这时候会继续往下执行 阻塞线程
10 if (p == head && tryAcquire(arg)) {
11 //如果抢到锁,将头节点后移(也就是将该节点设置为头结点)
12 setHead(node);
13 p.next = null; // help GC
14 failed = false;
15 return interrupted;
16 }
17 // 如果前置节点不是头结点,或者当前节点抢锁失败;通过shouldParkAfterFailedAcquire判断是否应该阻塞
18 // 当前置节点的状态为SIGNAL=-1,才可以安全被parkAndCheckInterrupt阻塞线程
19 if (shouldParkAfterFailedAcquire(p, node) &&
20 parkAndCheckInterrupt())
21 // 该线程已被中断
22 interrupted = true;
23 }
24 } finally {
25 if (failed)
26 cancelAcquire(node);
27 }
28 }
  • shouldParkAfterFailedAcquire(Node pred, Node node)

  检查和更新未能获取锁节点的状态,返回是否可以被安全阻塞。

 1     private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
2 int ws = pred.waitStatus; // 获取前置节点的状态
3 if (ws == Node.SIGNAL)
4 /*
5 * 前置节点的状态waitStatus为SIGNAL=-1,当前线程可以安全的阻塞
6 */
7 return true;
8 if (ws > 0) {
9 /*
10 * 如果前置节点的状态waitStatus>0,即waitStatus为CANCELLED=1(无效节点),需要从同步状态队列中取消等待(移除队列)
11 */
12 do {
13 node.prev = pred = pred.prev;
14 } while (pred.waitStatus > 0);
15 pred.next = node;
16 } else {
17 /*
18 * 将前置状态的waitStatus修改为SIGNAL=-1,然后当前节点才可以被安全的阻塞
19 */
20 compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
21 }
22 return false;
23 }
  • parkAndCheckInterrupt()

  阻塞当前节点,返回当前线程的中断状态。

1     private final boolean parkAndCheckInterrupt() {
2 LockSupport.park(this); //阻塞
3 return Thread.interrupted();
4 }
  • cancelAcquire(Node node)

  取消进行的获取锁操作,在非忽略中断模式下,线程被中断唤醒抛异常时会调用该方法。

 1     //  将当前节点的状态设置为CANCELLED,无效的节点,同时移除队列
2 private void cancelAcquire(Node node) {
3 if (node == null)
4 return;
5
6 node.thread = null;
7 Node pred = node.prev;
8 while (pred.waitStatus > 0)
9 node.prev = pred = pred.prev;
10
11 Node predNext = pred.next;
12 node.waitStatus = Node.CANCELLED;
13 if (node == tail && compareAndSetTail(node, pred)) {
14 compareAndSetNext(pred, predNext, null);
15 } else {
16 int ws;
17 if (pred != head &&
18 ((ws = pred.waitStatus) == Node.SIGNAL ||
19 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
20 pred.thread != null) {
21 Node next = node.next;
22 if (next != null && next.waitStatus <= 0)
23 compareAndSetNext(pred, predNext, next);
24 } else {
25 unparkSuccessor(node);
26 }
27
28 node.next = node; // help GC
29 }
30 }
  • hasQueuedPredecessors()

  判断当前线程是否应该排队。

  1.第一种结果——返回true:(1.1和1.2同时存在,1.2.1和1.2.2有一个存在)

    1.1  h != t为true,说明头结点和尾结点不相等,表示队列中至少有两个不同节点存在,至少有一点不为null。

    1.2  ((s = h.next) == null || s.thread != Thread.currentThread())为true

      1.2.1   (s = h.next) == null为true,表示头结点之后没有后续节点。

      1.2.2   (s = h.next) == null为false,s.thread != Thread.currentThread()为true
          表示头结点之后有后续节点,但是头节点的下一个节点不是当前线程

  2.第二种结果——返回false,无需排队。(2.1和2.2有一个存在)

    2.1  h != t为false,即h == t;表示h和t同时为null或者h和t是同一个节点,无后续节点。

    2.2  h != t为true,((s = h.next) == null || s.thread != Thread.currentThread())为false

      表示队列中至少有两个不同节点存在,同时持有锁的线程为当前线程。

1     public final boolean hasQueuedPredecessors() {
2 Node t = tail; // Read fields in reverse initialization order
3 Node h = head;
4 Node s;
5 return h != t &&
6 ((s = h.next) == null || s.thread != Thread.currentThread());
7 }

Java并发之AQS原理剖析的更多相关文章

  1. Java并发之AQS原理解读(三)

    上一篇:Java并发之AQS原理解读(二) 前言 本文从源码角度分析AQS共享锁工作原理,并介绍下使用共享锁的子类如何工作的. 共享锁工作原理 共享锁与独占锁的不同之处在于,获取锁和释放锁成功后,都会 ...

  2. Java并发之AQS原理解读(二)

    上一篇: Java并发之AQS原理解读(一) 前言 本文从源码角度分析AQS独占锁工作原理,并介绍ReentranLock如何应用. 独占锁工作原理 独占锁即每次只有一个线程可以获得同一个锁资源. 获 ...

  3. Java并发之AQS原理解读(一)

    前言 本文简要介绍AQS以及其中两个重要概念:state和Node. AQS 抽象队列同步器AQS是java.util.concurrent.locks包下比较核心的类之一,包括AbstractQue ...

  4. 并发之AQS原理(一) 原理介绍简单使用

    并发之AQS原理(一) 如果说每一个同步的工具各有各的强大,那么这个强大背后是一个相同的动力,它就是AQS. AQS是什么 AQS是指java.util.concurrent.locks包里的Abst ...

  5. 并发之AQS原理(三) 如何保证并发

    并发之AQS原理(三) 如何保证并发 1. 如何保证并发 AbstractQueuedSynchronizer 维护了一个state(代表了共享资源)和一个FIFO线程等待队列(多线程竞争资源被阻塞时 ...

  6. 并发之AQS原理(二) CLH队列与Node解析

    并发之AQS原理(二) CLH队列与Node解析 1.CLH队列与Node节点 就像通常医院看病排队一样,医生一次能看的病人数量有限,那么超出医生看病速度之外的病人就要排队. 一条队列是队列中每一个人 ...

  7. Java并发之AQS详解

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  8. Java并发之AQS详解(转)

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronized(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  9. 【转】Java并发的AQS原理详解

    申明:此篇文章转载自:https://juejin.im/post/5c11d6376fb9a049e82b6253写的真的很棒,感谢老钱的分享. 打通 Java 任督二脉 —— 并发数据结构的基石 ...

随机推荐

  1. [源码分析] 分布式任务队列 Celery 多线程模型 之 子进程

    [源码分析] 分布式任务队列 Celery 多线程模型 之 子进程 目录 [源码分析] 分布式任务队列 Celery 多线程模型 之 子进程 0x00 摘要 0x01 前文回顾 1.1 基类作用 1. ...

  2. 15个问题告诉你如何使用Java泛型

    摘要:Java泛型其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法. Java ...

  3. 各种平衡树收集(收集控(‐^▽^‐))\平衡树模板题的各种花式做法QAQ

    非旋转treap!!!(FHQ Treap) 递归版Splay(无需维护父指针) Scapegoat _ Tree--替罪羊树(一只(棵)特立独行的猪(树)) 宗法树(平衡线段树\finger_tre ...

  4. 源码篇:ThreadLocal的奇思妙想(万字图文)

    前言 ThreadLocal的文章在网上也有不少,但是看了一些后,理解起来总感觉有绕,而且看了ThreadLocal的源码,无论是线程隔离.类环形数组.弱引用结构等等,实在是太有意思了!我必须也要让大 ...

  5. 用vue-cli3搭建vue项目

    1.在nodejs官网下载node安装包,并进行安装:http://nodejs.cn/download/,在环境变量进行配置,并添加node_global和node_cache路径. 2.在D盘新建 ...

  6. UVA10294项链和手镯(等价类计数问题)

    题意:       给你一串珠子(连接成了一个环),共有n个珠子组成,你有t种颜色,现在你来给这个珠子染色,问染成项链有多少种方法?染成手镯有多少种方法?在项链里,经过顺时针旋转后相同的算一个,在手镯 ...

  7. Android系统自带的android.util.Base64的实现源码

    由于Android逆向还原的时候,经常需要用到android.util.Base64的代码,因此从Android 4.4.4的 系统里抠出来进行备份,懒得用其他的代码进行修改替换了. /* * Cop ...

  8. 11.PHP与MySQL

    PHP与MySQL 首先是PHPStorm设置创建SQL的教程,找到了一个写的不错的,在这里:http://blog.csdn.net/knight_quan/article/details/5198 ...

  9. 音视频开发:为什么推荐使用Jetpack CameraX?

    我们的生活已经越来越离不开相机,从自拍到直播,扫码再到VR等等.相机的优劣自然就成为了厂商竞相追逐的赛场.对于app开发者来说,如何快速驱动相机,提供优秀的拍摄体验,优化相机的使用功耗,是一直以来追求 ...

  10. 第三部分 IDEA创建并运行项目

    可以创建一个maven,几行代码就解决了导入依赖,但是我的电脑不知道哪里出现了问题,IDEA重装,jdk重装,maven重装,都无法解决问题,找了3天,还是没有解决问题.最后只能采用手动导入包方法.看 ...