To use this class as the basis of a synchronizer, redefine the
* following methods, as applicable, by inspecting and/or modifying
* the synchronization state using {@link #getState}, {@link
* #setState} and/or {@link #compareAndSetState}:
*
* <ul>
* <li> {@link #tryAcquire}
* <li> {@link #tryRelease}
* <li> {@link #tryAcquireShared}
* <li> {@link #tryReleaseShared}
* <li> {@link #isHeldExclusively}
* </ul>

上面这段话是AQS源码的一段注解,意思是使用AQS实现一个同步器的话需要覆盖实现上面li标签中的这些方法,并且使用getState、setState、compareAndSetState这几个方法来对状态进行操作。

如果你对JDK源码较为熟悉的话,你会发现AQS(AbstractQueuedSynchronizer)是并发过程中很常见的一个抽象类,我们常用的CountDownLatch、ReentrantLock、FutureTask(1.8不再使用AQS)、Semaphore等类都是在内部定义了一个叫做Sync的内部类,而Sync类继承了AQS抽象类并重写了一些必要的方法。可重入锁ReentrantLock中,state可以用来表示当前线程获取锁的可重入次数;对于读写锁ReentrantReadWriteLock来说,state的高16位表示获取到的读锁的线程的可重入次数,低16位是写锁;对于Semaphore来说,state用来表示当前可用信号的个数;对于CountDownlatch来说,state用来表示计数器当前的值

 abstract static class Sync extends AbstractQueuedSynchronizer {
...
...
...

下面说说AQS到底是用来做什么的:

AQS抽象类的内部是一个final的Node类,类中定义了一个volatile的整形状态量state,通过这个Node类来建立一个FIFO的线程等待队列(多线程抢占资源失败被阻塞时会进入此队列)。

static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null; /** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;

而这个队列中的每个结点对应一个线程,结点内封装了这个线程的基本信息,状态和等待的资源类型等。

     * <p>To enqueue into a CLH lock, you atomically splice it in as new
* tail. To dequeue, you just set the head field.
* <pre>
* +------+ prev +-----+ +-----+
* head | | <---- | | <---- | | tail
* +------+ +-----+ +-----+
* </pre>
*
* <p>Insertion into a CLH queue requires only a single atomic
* operation on "tail", so there is a simple atomic point of
* demarcation from unqueued to queued. Similarly, dequeuing
* involves only updating the "head". However, it takes a bit
* more work for nodes to determine who their successors are,
* in part to deal with possible cancellation due to timeouts
* and interrupts.

这里借用网上的一张图来说明这个队列的框架。

AQS定义了两种资源共享的方式,独占和共享,Exclusive和Share,前者常用的是ReentrantLock,后者常用的是前面提到的CountDownLatch、Semaphore等。

继承AQS,实现state标志位的获取、释放方式,即可实现自定义同步器,至于具体的等待队列的维护、阻塞入队、唤醒出队等在AQS中已经实现好了。标志位涉及到的实现函数如下:

  • isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。

  • tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。

  • tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。

  • tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。

  • tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。


 自定义同步器

独占锁以ReentrantLock为例,state初始值为0,表示未锁状态,当一个线程执行了lock()方法之后,会调用tryAcquire方法独占该锁并将state+1,此后其他线程尝试获取锁时便会失败,进入队列,知道刚才的线程释放锁将state值更改为0时,其他线程才有机会获取这个独占锁。当然,这个锁时可重入的,获得锁的线程可以继续state+1,不过只有state为0的时候其他线程才会唤醒。

共享锁以CountDownLatch为例,初始化线程数为n,及将state初始值设置为n,表示可以有n个线程同时获取这个共享锁,当有线程执行一countDown(),state就会CAS的方式减少1,知道所有线程执行完,state值为0的时候,会unpark主线程,然后主线程会从await状态被唤醒返回,继续执行其他指令。

一般情况自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。需要说明的是AQS也支持同时实现独占同步和共享同步,比如ReentrantReadWriteLock。


通常,如果一个线程没有拿到锁,便会被封装成结点加入队尾,检查状态之后会调用park方法进入waiting状态,等待unpark方法或interrupt方法唤醒自己,当它被唤醒之后会检查自己是否有资格拿到锁,如果成功,head会指向当前节点,并检查是否被中断过;否则会继续等待。

AQS是支持中断的,比如acquireInterruptibly方法和acquireSharedInterruptibly方法。下面将书上看到的互斥锁Mutex源码贴在下面供大家研究,它不支持重入,只有0(未锁定)和1(锁定)两种状态:

class Mutex implements Lock, java.io.Serializable {
// 自定义同步器
private static class Sync extends AbstractQueuedSynchronizer {
// 判断是否锁定状态
protected boolean isHeldExclusively() {
return getState() == 1;
} // 尝试获取资源,立即返回。成功则返回true,否则false。
public boolean tryAcquire(int acquires) {
assert acquires == 1; // 这里限定只能为1个量
if (compareAndSetState(0, 1)) {//state为0才设置为1,不可重入!
setExclusiveOwnerThread(Thread.currentThread());//设置为当前线程独占资源
return true;
}
return false;
} // 尝试释放资源,立即返回。成功则为true,否则false。
protected boolean tryRelease(int releases) {
assert releases == 1; // 限定为1个量
if (getState() == 0)//既然来释放,那肯定就是已占有状态了。只是为了保险,多层判断!
throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);//释放资源,放弃占有状态
return true;
}
} // 真正同步类的实现都依赖继承于AQS的自定义同步器!
private final Sync sync = new Sync(); //lock<-->acquire。两者语义一样:获取资源,即便等待,直到成功才返回。
public void lock() {
sync.acquire(1);
} //tryLock<-->tryAcquire。两者语义一样:尝试获取资源,要求立即返回。成功则为true,失败则为false。
public boolean tryLock() {
return sync.tryAcquire(1);
} //unlock<-->release。两者语文一样:释放资源。
public void unlock() {
sync.release(1);
} //锁是否占有状态
public boolean isLocked() {
return sync.isHeldExclusively();
}
}

 

AQS原理及应用的更多相关文章

  1. AQS原理以及AQS同步组件总结

    1 AQS简单介绍 2 AQS原理

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

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

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

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

  4. 简述AQS原理

    这是一道面试题:简述AQS原理 AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态.如果被请求的共享资源被占用,那么就需要一套线程阻塞 ...

  5. AQS原理

    1. AQS原理 1.1. 是什么 AQS全程AbstractQueuedSynchronizer抽象队列同步器,它是并发包中的基础类 ReetrantLock,ReentrantReadWriteL ...

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

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

  7. AQS 原理以及 AQS 同步组件总结

    1 AQS 简单介绍 AQS 的全称为(AbstractQueuedSynchronizer),这个类在 java.util.concurrent.locks 包下面. AQS 是一个用来构建锁和同步 ...

  8. 浅谈AQS原理

    一.AQS介绍 AQS,即AbstractQueuedSynchronizer, 抽象队列同步器,它是Java多线程模块用来构建锁和其他同步组件的基础框架.来看下同步组件对AQS的使用: AQS是一个 ...

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

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

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

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

随机推荐

  1. P1144 最短路计数 题解 最短路应用题

    题目链接:https://www.luogu.org/problem/P1144 其实这道题目是最短路的变形题,因为数据范围 \(N \le 10^6, M \le 2 \times 10^6\) , ...

  2. poj 1263 Reflections (Simple Geometry)

    1263 -- Reflections 简单计算几何.题目给出射线以及若干个不相交的圆,求出射线会在哪些圆上反弹,依次写出反弹球的编号. 代码如下: #include <cstdio> # ...

  3. gradle在build的时候找不到某个jar包的解决办法

    前几天公司来新人, 我给他装项目环境的时候遇到一个问题, 在执行gradle build时遇到一系列的错误, 我一个个分析并解决了, 特此记录, 以供他人参考. 一, 首先遇到了找不到spring-b ...

  4. Python--day69--ORM外键的正向查询和反向查询

    什么是正向查询,什么是方向查询?

  5. 浅谈Transformer 及Attention网络

    1 Transformer 模型结构处理自然语言序列的模型有 rnn, cnn(textcnn),但是现在介绍一种新的模型,transformer.与RNN不同的是,Transformer直接把一句话 ...

  6. 2018-8-10-win10-sdk-是否向下兼容

    title author date CreateTime categories win10 sdk 是否向下兼容 lindexi 2018-08-10 19:16:53 +0800 2018-2-13 ...

  7. [C++] 烦人的error LNK2019和error LNK2001

    常见原因: 没有正确的设置引用的lib,新手常犯这个错误,这是最容易解决的问题. extern "C"的问题.如果C++写的dll要给C程序用,那么就要注意extern " ...

  8. Linux创建用户、设置密码、修改用户、删除用户命令

    与大家分享下Linux系统中创建用户.设置密码.修改用户.删除用户的命令,希望对你有所帮助. useradd testuser  创建用户testuserpasswd testuser  给已创建的用 ...

  9. 2018-8-10-win10-uwp-商业游戏-1.2.1

    title author date CreateTime categories win10 uwp 商业游戏 1.2.1 lindexi 2018-08-10 19:16:50 +0800 2018- ...

  10. CodeForce - 1189 D1. Add on a Tree (思维题)

    Note that this is the first problem of the two similar problems. You can hack this problem only if y ...