先做总结:

1、AbstractQueuedSynchronizer是什么?

AbstractQueuedSynchronizer(AQS)这个抽象类,是Java并发包 java.util.concurrent 的基础工具类,是实现 ReentrantLock、CountDownLatch、Semaphore、FutureTask 等类的基础。

AbstractQueuedSynchronizer其实是锁的主体。Lock类会有一个AQS类型的属性,来实现锁。

2、AQS如何实现锁?

(1)Lock类会有一个AQS类型的属性sync,sync是AQS的子类,重写了tryAcquire()方法(获取锁)和tryRelease()方法(释放锁)。

(2)线程T获取到锁标志:AQS.state > 0  && AQS.exclusiveOwnerThread == 当前线程T。tryAcquire()/tryRelease()方法其实就是对AQS.state AQS.exclusiveOwnerThread 的操作。

3、AQS原理:

(1)AQS中维护着一个同步队列。

  头结点headNode只能是null或者是已经获取到锁的线程,只有第二个节点能够尝试获取锁。

  第二个节点获取到锁之后变为头结点。

(2)acquire()方法会调用tryAcquire()获取锁。tryAcquire()获取到锁,完成。

  如果获取锁失败(没有竞争到锁),当前线程T会封装成Node插入同步队列中,并且将当前线程T park()。

(3)release()方法调用tryRelease()方法释放锁,当前线程释放锁之后,会unpark()下一节点(也就是唤醒第二节点,因为持有锁的一定是头节点线程或者不在队列中的线程)

一、CLH同步队列

AQS通过内置的FIFO同步队列来完成资源获取线程的排队工作。

如果当前线程获取同步状态失败(锁)时,AQS则会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列,同时会park当前线程;

当同步状态释放时,则会把节点中的线程唤醒,使其再次尝试获取同步状态。

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;// 当前node的后继节点对应的线程需要被唤醒(表示后继节点的状态)
static final int CONDITION = -2;// 当前节点线程状态 0-没有获得锁 >0-线程取消了等待
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;// 每一个节点对应一个线程
Node nextWaiter;// 共享模式/独占模式
}

入列:

    private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {// 加入队尾
node.prev = pred;
if (compareAndSetTail(pred, node)) {// 失败:其它线程抢先入列了
pred.next = node;
return node;
}
}
enq(node);
return node;
} private Node enq(final Node node) {
for (;;) {// 循环入列,直到成功
Node t = tail;
if (t == null) {// 初始化head
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {// 失败:其它线程抢先入列了
t.next = node;
return t;
}
}
}
}

两个方法都是通过一个CAS方法compareAndSetTail(Node expect, Node update)来设置尾节点,该方法可以确保节点是线程安全添加的

二、属性

    private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;// 0代表没有被占用,大于0代表有线程持有当前锁(锁可以重入,每次重入都+1)
private transient Thread exclusiveOwnerThread; // 继承自AbstractOwnableSynchronizer 当前持有锁的线程

三、主要方法

    getState()// 返回同步状态的当前值;
setState(int newState)// 设置当前同步状态;
compareAndSetState(int expect, int update)// 使用CAS设置当前状态,该方法能够保证状态设置的原子性; acquire(int arg)// 独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用可重写的tryAcquire(int arg)方法;
acquireInterruptibly(int arg)// 与acquire(int arg)相同,但是该方法响应中断,当前线程为获取到同步状态而进入到同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException异常并返回;
tryAcquireNanos(int arg,long nanos)// 超时获取同步状态,如果当前线程在nanos时间内没有获取到同步状态,那么将会返回false,已经获取则返回true;
acquireShared(int arg)// 共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态;
acquireSharedInterruptibly(int arg)// 共享式获取同步状态,响应中断;
tryAcquireSharedNanos(int arg, long nanosTimeout)// 共享式获取同步状态,增加超时限制;
release(int arg)// 独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒;
releaseShared(int arg)// 共享式释放同步状态; tryAcquire(int arg)// 独占式获取同步状态;通过子类重写实现
tryRelease(int arg)// 独占式释放同步状态;通过子类重写
tryAcquireShared(int arg)// 共享式获取同步状态;子类重写
tryReleaseShared(int arg)// 共享式释放同步状态;子类重写

四、以独占锁为例解析AQS(共享锁几乎一样)

AQS的设计模式采用的模板方法模式,子类通过继承的方式,实现它的抽象方法来管理同步状态,对于子类而言它并没有太多的活要做,AQS提供了大量的模板方法来实现同步。

独占式同步状态获取

   public final void acquire(int arg) {
if (!tryAcquire(arg) && // 尝试获取锁-由子类重写
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 获取锁失败: addWaiter()-当前线程new Node+入列; acquireQueued()-设置前驱节点状态-1,当前线程park()
selfInterrupt();
} /**
* 1.尝试获取锁 获取成功,将当前节点置为头结点
* 2.获取锁失败,将前驱节点状态置为-1,当前节点线程park()等待
* 3.前驱点释放锁时,会将当前节点unpark(),继续自旋获取锁
*/
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)) { // 前驱节点是head时,当前节点才能请求锁(请求锁的是第二个节点)
setHead(node); // 当前线程获取到锁之后,将当前节点置为头结点
p.next = null;
failed = false;
return interrupted;
} if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
} /**
* 1.park开头的方法来阻塞当前线程,unpark(Thread thread)方法来唤醒一个被阻塞的线
* 2.前驱点 waitStatus==-1,当前节点线程才能park
* 3.ws>0表示已经获取过锁,从CLH队列删除,CLH队列存放没有获取到锁被挂起的线程节点
* 4.当前线程没有获取到锁,需要设置前驱节点状态为-1,这样当前节点线程才能park()
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; // 前驱节点
if (ws == Node.SIGNAL) // 当前节点线程可以park
return true;
if (ws > 0) { // ws>0 已经获取过锁,从CLH队列删除
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else { // 需要设置前驱节点状态为-1,这样当前节点线程才能park()
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
} /**
* 当前线程park()等待,直到unpark()
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}

acquire(int arg)方法流程图如下:

独占式同步状态释放

    public final boolean release(int arg) {
if (tryRelease(arg)) { // 尝试释放锁-由子类重写
Node h = head; // 头结点占有锁(原因:第二节点获取到锁之后被置为头结点)
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
} 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;
// 从队尾往前找,找到waitStatus<=0的所有节点中排在最前面的
// 从队尾往前找原因:node.next可能会存在null或者取消了。入列时是先设置node.prev,CAS之后再设置node.next
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null) // 唤醒下一节点
LockSupport.unpark(s.thread);
}

参考资料 / 相关推荐:

一行一行源码分析清楚AbstractQueuedSynchronizer (超详细,一定能看懂)

【死磕Java并发】—–J.U.C之AQS:AQS简介

【JUC】JDK1.8源码分析之AbstractQueuedSynchronizer(二)

Java并发(八):AbstractQueuedSynchronizer的更多相关文章

  1. Java并发框架AbstractQueuedSynchronizer(AQS)

    1.前言 本文介绍一下Java并发框架AQS,这是大神Doug Lea在JDK5的时候设计的一个抽象类,主要用于并发方面,功能强大.在新增的并发包中,很多工具类都能看到这个的影子,比如:CountDo ...

  2. java 并发(五)---AbstractQueuedSynchronizer(4)

    问题 : rwl 的底层实现是什么,应用场景是什么 读写锁 ReentrantReadWriteLock 首先我们来了解一下 ReentrantReadWriteLock 的作用是什么?和 Reent ...

  3. java 并发(五)---AbstractQueuedSynchronizer

    文章部分图片和代码来自参考文章. LockSupport 和 CLH 和 ConditionObject 阅读源码首先看一下注解 ,知道了大概的意思后,再进行分析.注释一开始就进行了概括.AQS的实现 ...

  4. Java并发基础类AbstractQueuedSynchronizer的实现原理简介

    1.引子 Lock接口的主要实现类ReentrantLock 内部主要是利用一个Sync类型的成员变量sync来委托Lock锁接口的实现,而Sync继承于AbstractQueuedSynchroni ...

  5. Java并发编程-AbstractQueuedSynchronizer源码分析

    简介 提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架.该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础.使用的方法是继承,子类通过 ...

  6. java 并发(五)---AbstractQueuedSynchronizer(5)

    问题 : ArrayBlockQueue 和 LinkedBlockQueue 的区别 两者的实现又是怎么样的 应用场景 BlockingQueue 概述 blockingQueue 是个接口,从名字 ...

  7. Java并发:AbstractQueuedSynchronizer(AQS)

    队列同步器 AbstractQueuedSynchronizer 是一个公共抽象类.提供一个同步器框架,用于实现依赖于先进先出(FIFO)等待队列的阻塞锁和相关同步器(信号量,事件等).使用一个 in ...

  8. java 并发(五)---AbstractQueuedSynchronizer(3)

           文章代码分析和部分图片来自参考文章 问题 : CountDownLatch  和 CyclicBarrier 的区别 认识 CountDownLatch 分析这个类,首先了解一下它所可以 ...

  9. java 并发(五)---AbstractQueuedSynchronizer(2)

           文章部分代码和照片来自参考资料 问题 : ConditionObject  的 await 和 signal 方法是如何实现的 ConditonObject ConditionObjec ...

  10. Java并发编程Semaphore

    信号量 信号量类Semaphore,用来保护对唯一共享资源的访问.一个简单的打印队列,并发任务进行打印,加入信号量同时之能有一个线程进行打印任务 . import java.util.concurre ...

随机推荐

  1. python作业高级FTP(第八周)

    作业需求: 1. 用户加密认证 2. 多用户同时登陆 3. 每个用户有自己的家目录且只能访问自己的家目录 4. 对用户进行磁盘配额.不同用户配额可不同 5. 用户可以登陆server后,可切换目录 6 ...

  2. Tensorflow中使用TFRecords高效读取数据--结合Attention-over-Attention Neural Network for Reading Comprehension

    原文链接:https://arxiv.org/pdf/1607.04423.pdf 本片论文主要讲了Attention Model在完形填空类的阅读理解上的应用. 转载:https://blog.cs ...

  3. WordPress手机端插件——WPtouch

    戒微博之后,把更多的精力开始转投回网站上来:今天用nexus7访问@Bee君 的博客时,发现博客的界面与电脑上访问的界面不相同,顺藤摸瓜之后发现原来bee君使用的是WPtouch-pro插件来实现移动 ...

  4. Linux SCIM/fcitx/ibus 输入法

    现在很多发行版linux一般都是装好scim scim-tables-zh 重启就行 但有时重启后还是不能调用 可以用如下方法: 添加文件: sudo gedit /etc/X11/xinit/xin ...

  5. python进阶之关键字和运算符触发魔法方法

    前言 python有众多的魔法方法,它们会在满足某种条件下触发执行,掌握好魔法方法的使用,可以加快程序的运行效率,同时减少逻辑调用. 关键字与魔法方法 python的一些魔法方法是关键字触发的,即py ...

  6. python设计模式之内置装饰器使用(四)

    前言 python内部有许多内建装饰器,它们都有特别的功能,下面对其归纳一下. 系列文章 python设计模式之单例模式(一) python设计模式之常用创建模式总结(二) python设计模式之装饰 ...

  7. windows安装React Native开发运行环境

    React Native是什么 React Native是facebook开源的一个用于开发app的框架.React Native的设计理念:既拥有Native (原生) 的用户体验.又保留React ...

  8. salt-api起不来:ImportError('No module named wsgiserver2',)

    问题:启动salt-api时没有报错但是没有端口,查看/var/log/salt/api发现如下报错: 解决方法: 下载wsgiserver2文件,放到/usr/lib64/python2.7/sit ...

  9. postman中 form-data、x-www-form-urlencoded、raw、binary的区别 && 下载文件

    1.form-data:  就是http请求中的multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开.既可以上传键值对,也可以上传文件.当上传的字段是文件 ...

  10. 使用FormData数据做图片上传: new FormData() canvas实现图片压缩

    使用FormData数据做图片上传: new FormData()       canvas实现图片压缩 ps: 千万要使用append不要用set   苹果ios有兼容问题导致数据获取不到,需要后台 ...