AQS是并发编程的一个最基本组件,是一个抽象同步器。

网上有很多详细介绍AQS的博文,在这里我就不仔细介绍了,主要写一些重要的内容。

AQS中重要的几个属性:

//同步队列的头节点
private transient volatile Node head;
//同步队列的尾节点
private transient volatile Node tail;
//同步状态
private volatile int state;

由于一个共享资源同一时间可以被一条线程持有,也可以被多个线程持有,因此AQS中存在两种模式,共享模式独占模式

  • 共享模式是共享状态值state每次可以由多个线程持有,如CountDownLatchSemaphore
  • 独占模式是共享状态值state每次只能由一条线程持有,其他线程如果需要获取,则需要阻塞。如ReentrantLock

独占锁的获取

public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

先尝试获取锁,获取失败会调用addWaiter将当前线程添加到同步队列,之后队列中的每个节点会调用acquireQueued()方法通过自旋的方式先再尝试获取一下锁,如果失败,将当前节点的前驱节点的状态设置为SIGNAL,并将该线程阻塞,并判断该线程是否被中断。如果被中断了,当前节点获取锁后进行中断操作。

这里用到了模版方法的设计模式,tryAcquire是一个抽象方法,具体实现需要到子类中去完成。

 protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}

下面详细介绍获取独占锁失败后,添加到队列的过程,调用addWaiter()方法。

private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}

先创建出一个节点

  1. 当前尾节点不为空,采用尾插法利用CAS机制将新创建的节点添加到尾部,设置为尾节点。如果添加失败,就执行enq()方法。
  2. 如果尾节点为空,调用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;
}
}
}
}

enq是一个自旋操作,

1) 如果尾节点为空,说明当前线程是第一个加入同步队列的,先用CAS操作新添一个头节点head,并将尾节点指向它。第二次循环会跳往另一个执行区域。

2) 利用CAS操作将该节点添加到尾部。

直到自旋添加成功,就结束循环。

入队成功后,就要为该节点开启自旋,尝试获得锁。

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

先获取当前节点的先驱节点

1) 如果先驱接节点是头节点并且成功获取同步状态的时候,将当前节点设置为头节点,然后将之前的头节点的next指针设置为null并且pre指针也为null,即将前节点与队列断开,

2)如果获取失败,就调用shouldParkAfterFailedAcquire方法,主要作用是将该节点的前驱节点的状态设置为SIGNAL,表示线程阻塞。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}

shouldParkAfterFailedAcquire方法中,如果前驱节点的状态是SIGNAL,就直接返回true;如果状态值大于0,表明该前驱节点已经被取消,则将该前驱节点去除掉,向前移;其他情况,将该前驱节点设置为SIGNAL。如果添加失败,就返回false,因为是自旋,下一次再尝试。

由于acquireQueued方法是一个循环,在第二次执行到shouldParkAfterFailedAcquire方法时,由于0号节点的waitStatus已经为Node.SIGNAL了,所以shouldParkAfterFailedAcquire方法会返回true,然后继续执行parkAndCheckInterrupt方法,将该线程已经阻塞,并怕判断该线程是否中断。

private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}

独占锁的释放

public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

独占锁通过tryRelease释放成功后,如果头节点head不为null,并且状态值不为0,就会对它的后继节点进行唤醒。

private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//头节点的后继节点
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)
//后继节点不为null时唤醒该线程
LockSupport.unpark(s.thread);
}

如果头节点的后继节点是空或者它的状态值大于0,表明它是失效的,就要从尾节点节点向前查找,找到最后一个状态值小于等于0的节点,然后对该节点进行唤醒。

共享锁的获取与释放

public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}

共享锁可以同时被多个线程拥有,可以在初始设置的时候将state设置为大于0的值,每一个线程获取一次,就减1,当state大于等于0时,别的线程也能够拥有该锁,当小于0时,就不可以,在共享锁模式下,当前线程拿到锁后,会直接通知后继节点去拿锁,而不必等待锁被释放的时候再通知。 在锁释放的时候,支持多个线程释放同步线程同步状态。

参考文章:

深入理解AbstractQueuedSynchronizer(AQS)

java并发编程系列:牛逼的AQS(上)

AQS源码分析总结的更多相关文章

  1. ReentrantLock 与 AQS 源码分析

    ReentrantLock 与 AQS 源码分析 1. 基本结构    重入锁 ReetrantLock,JDK 1.5新增的类,作用与synchronized关键字相当,但比synchronized ...

  2. 并发-AQS源码分析

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

  3. AQS源码分析笔记

    经过昨晚的培训.对AQS源码的理解有所加强,现在写个小笔记记录一下 同样,还是先写个测试代码,debug走一遍流程, 然后再总结一番即可. 测试代码 import java.util.concurre ...

  4. AQS源码分析看这一篇就够了

      好了,我们来开始今天的内容,首先我们来看下AQS是什么,全称是 AbstractQueuedSynchronizer 翻译过来就是[抽象队列同步]对吧.通过名字我们也能看出这是个抽象类 而且里面定 ...

  5. JAVA AQS源码分析

    转自:  http://www.cnblogs.com/pfan8/p/5010526.html JAVA AQS的全称为(AbstractQueuedSynchronizer),用于JAVA多线程的 ...

  6. AbstractQueuedSynchronizer AQS源码分析

    申明:jdk版本为1.8 AbstractQueuedSynchronizer是jdk中实现锁的一个抽象类,有排他和共享两种模式. 我们这里先看排他模式,共享模式后面结合java.util.concu ...

  7. AQS源码分析

    AQS全程为AbstractQueuedSynchronizer,其定义了一套多线程访问共享资源的同步框架,大部分的同步类的实现都依赖于他,比如ReentrantLock,ReentrantReadW ...

  8. java中AQS源码分析

    AQS内部采用CLH队列.CLH队列是由节点组成.内部的Node节点包含的状态有 static final int CANCELLED =  1; static final int SIGNAL    ...

  9. ArrayList源码分析--jdk1.8

    ArrayList概述   1. ArrayList是可以动态扩容和动态删除冗余容量的索引序列,基于数组实现的集合.  2. ArrayList支持随机访问.克隆.序列化,元素有序且可以重复.  3. ...

随机推荐

  1. Docker 容器数据 持久化(系统学习Docker05)

    写在前面 本来是可以将数据存储在 容器内部 的.但是存在容器内部,一旦容器被删除掉或者容器毁坏(我亲身经历的痛,当时我们的大数据平台就是运行在docker容器内,有次停电后,不管怎样容器都起不来.以前 ...

  2. pycham设置头文件内容

    pycharm软件 设置头文件方法 File->settings->Editor->File and Code Templates->Python Script  #!/usr ...

  3. Codeforces_739_B

    http://codeforces.com/problemset/problem/739/B dfs,记录距离前缀和,每次找到离最近的不符合的点. #include<iostream> # ...

  4. POJ_1006_中国剩余

    http://poj.org/problem?id=1006 中国剩余定理用来解求模方程组,用到了逆元. 这题三个数互质,直接用扩展欧几里德可得逆元. #include<iostream> ...

  5. Codevs 1205 单词反转(Vector以及如何输出string)

    题意:倒序输出句子中的单词 代码: #include<cstdio> #include<iostream> #include<string> #include< ...

  6. java设计模式7——桥接模式

    java设计模式7--桥接模式 1.桥接模式介绍 桥接模式是将抽象部分与它的实现部分分离,使他们都可以独立的变化.它是一种对象结构型模式,又称为柄体模式或接口模式. 2.解决问题 2.1.将复杂的组合 ...

  7. Go语言实现:【剑指offer】连续子数组的最大和

    该题目来源于牛客网<剑指offer>专题. HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学.今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向 ...

  8. Go语言实现:【剑指offer】二叉搜索树的后序遍历序列

    该题目来源于牛客网<剑指offer>专题. 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字都互不相同. Go ...

  9. Xcode11: 删除默认Main.storyBoard, 自定义UIWindow的变化 UIWindow 不能在AppDelegate中处理

    Xcode自动新增了一个SceneDelegate文件,查找了一下官方文档WWDC2019:Optimizing App Launch 发现,iOS13中appdelegate的职责发现了改变: iO ...

  10. javascript 对象api

    // Object 构造函数的属性: Object.prototype//可以为所有 Object 类型的对象添加属性 class A extends B{ constructor(){ super( ...