JUC并发编程基石AQS源码之结构篇
前言
AQS(AbstractQueuedSynchronizer)算是JUC包中最重要的一个类了,如果你想了解JUC提供的并发编程工具类的代码逻辑,这个类绝对是你绕不过的。我相信如果你是第一次看AQS源码肯定是一脸懵逼,一个个方法跳来跳去一会就绕蒙了。所以把整个代码骨架搞明白是你看懂AQS源码的第一步。本篇文章只说代码结构,之后的篇章会讲解AQS具体的执行逻辑。
顶级接口Lock
public interface Lock {
void lock();
void unlock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
Condition newCondition();
}
我们一直强调面向接口编程的思想,看源码也是先从接口入手。Lock算是JUC提供的锁中的顶级接口,我们平常使用的ReentrantLock、ReadWriteLock等直接或间接的都实现了这个接口。这个接口主要是用来规定我们怎样去加锁和解锁,将加锁和解锁的方法暴露出去。下面的伪代码是一个典型的加锁解锁方式,多么简单,这就是面向接口的艺术。
Lock l = ...;
l.lock();
try {
// 自己的逻辑代码
} finally {
l.unlock();
}
代码结构
到现在我们知道了怎么加锁和解锁的方式,下一步自己通过接口去实现一个加锁类。这个比你直接看源码更重要。Doug Lea(AQS源码作者)大神在AQS类注解上就给我们提供了一个示例类Mutex。看源码类注解和方法注解也是你理解源码的一个重要渠道,还可以锻炼自己的英文。
实现Lock接口
首先要实现Lock接口,将加锁和解锁方法暴露出去。我们以加锁为例
class Mutex implements Lock, java.io.Serializable {
public void lock() {
sync.acquire(1);
}
代码很简单,调用了sync.acquire(1)方法,所以这个方法是加锁逻辑的入口,往下看
内部类继承AQS
现在将加锁方法暴露出去了,具体的加锁逻辑则需要AQS类了。AQS是一个抽象类,我们要使用它则需要继承,实现抽象方法。AQS加锁采用的是模板的设计模式,加锁以及锁失败的后续处理的整体流程代码已经实现,我们只需要实现我们需要的具体加锁方式即可。
class Mutex implements Lock, java.io.Serializable {
// The sync object does all the hard work. We just forward to it.
private final Sync sync = new Sync();
// 内部类继承AQS
private static class Sync extends AbstractQueuedSynchronizer {
// 实现抽象方法
public boolean tryAcquire(int acquires) {
assert acquires == 1; // Otherwise unused
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
}
}
我们会发现Sync继承了AQS,但是没有sync.acquire()这个方法,那么这个方法肯定来源于父类了。
AQS的acquire方法
public abstract class AbstractQueuedSynchronizer{
//加锁的入口方法,模板的设计模式
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//具体的加锁逻辑,需要自己实现
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
acquire方法定义了整个的加锁流程,而且使用了设计模式中的模板模式。
整体调用流程
从上面的流程可以看出,就这四个步骤就涉及到了三个类
Mutex.lock为暴露出去的加锁方法
AQS.acquire是加锁的模板方法,实现了加锁逻辑的整个流程
Sync.tryAcquire,图中的绿色部分,是一个抽象方法需要自己实现,针对不同的锁类型如公平锁、非公平锁、共享锁、独占锁等有不同的实现方式。
AQS.acquireQueued是加锁失败后的逻辑,将线程入队,这个后面讲AQS源码会重点讲。
解锁操作的流程和加锁类似,读者可以自己看一下解锁的流程。
自定义加锁类的源码
下面的代码是Mutex类的整体代码,有需要的可以在自己的IDE中感受一下整体的结构。
class Mutex implements Lock, java.io.Serializable {
public static void main(String[] args) {
Lock lock = new Mutex();
lock.lock();
try {
}finally {
lock.unlock();
}
}
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
// Our internal helper class
private static class Sync extends AbstractQueuedSynchronizer {
// Reports whether in locked state
protected boolean isHeldExclusively() {
return getState() == 1;
}
// Acquires the lock if state is zero
public boolean tryAcquire(int acquires) {
assert acquires == 1; // Otherwise unused
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// Releases the lock by setting state to zero
protected boolean tryRelease(int releases) {
assert releases == 1; // Otherwise unused
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// Provides a Condition
Condition newCondition() {
return new ConditionObject();
}
// Deserializes properly
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
// The sync object does all the hard work. We just forward to it.
private final Sync sync = new Sync();
public boolean tryLock() {
return sync.tryAcquire(1);
}
public Condition newCondition() {
return sync.newCondition();
}
public boolean isLocked() {
return sync.isHeldExclusively();
}
public boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
}
下一篇我们将根据上面所说的来分析ReentrantLock类的代码结构
如有不实,还望指正
JUC并发编程基石AQS源码之结构篇的更多相关文章
- JUC 并发编程--11, AQS源码原理解析, ReentrantLock 源码解读
这里引用别人博客,不重复造轮子 https://blog.csdn.net/u012881584/article/details/105886486 https://www.cnblogs.com/w ...
- JUC并发编程基石AQS之主流程源码解析
前言 由于AQS的源码太过凝练,而且有很多分支比如取消排队.等待条件等,如果把所有的分支在一篇文章的写完可能会看懵,所以这篇文章主要是从正常流程先走一遍,重点不在取消排队等分支,之后会专门写一篇取消排 ...
- 多线程进阶——JUC并发编程之CountDownLatch源码一探究竟
1.学习切入点 JDK的并发包中提供了几个非常有用的并发工具类. CountDownLatch. CyclicBarrier和 Semaphore工具类提供了一种并发流程控制的手段.本文将介绍Coun ...
- 多线程高并发编程(3) -- ReentrantLock源码分析AQS
背景: AbstractQueuedSynchronizer(AQS) public abstract class AbstractQueuedSynchronizer extends Abstrac ...
- JUC锁:核心类AQS源码详解
目录 1 疑点todo和解疑 2 AbstractQueuedSynchronizer学习总结 2.1 AQS要点总结 2.2 细节分析 2.2.1 插入节点时先更新prev再更新前驱next 2.2 ...
- 并发编程之 AQS 源码剖析
前言 JDK 1.5 的 java.util.concurrent.locks 包中都是锁,其中有一个抽象类 AbstractQueuedSynchronizer (抽象队列同步器),也就是 AQS, ...
- 多线程高并发编程(10) -- ConcurrentHashMap源码分析
一.背景 前文讲了HashMap的源码分析,从中可以看到下面的问题: HashMap的put/remove方法不是线程安全的,如果在多线程并发环境下,使用synchronized进行加锁,会导致效率低 ...
- 多线程高并发编程(7) -- Future源码分析
一.概念 A Future计算的结果. 提供方法来检查计算是否完成,等待其完成,并检索计算结果. 结果只能在计算完成后使用方法get进行检索,如有必要,阻塞,直到准备就绪. 取消由cancel方法执行 ...
- 并发编程实战-ConcurrentHashMap源码解析
jdk8之前的实现原理 jdk1.7中采用的数据结构是Segment + HashEntry 的方式进行实现.主要的结构如下图: ConcurrentHashMap 并不是将每个方法都在同一个锁上同步 ...
随机推荐
- NIO的原理和文件读入读出及图片拷贝的使用
1.NIO的简介 java.nio 全称 java non-blocking IO 是jdk1.4之后出现的 New IO 为所有的原始类型(boolean除外)提供了缓存 ...
- java实现SSO(SingleSignOn)单点登录服务
单点登录SSO:是指用户通过一次登录,可以访问任意所有相互信任的应用系统.即一处登录,处处登录.比如阿里系下的淘宝.天猫等,虽然是不同的产品,但归于一个体系下,是可以相互信任的应用系统. 为了方便用户 ...
- CodeForces 196B Infinite Maze
Infinite Maze time limit per test 2 seconds memory limit per test 256 megabytes input standard input ...
- OpenCV-Python 傅里叶变换 | 三十
目标 在本节中,我们将学习 使用OpenCV查找图像的傅立叶变换 利用Numpy中可用的FFT函数 傅立叶变换的某些应用程序 我们将看到以下函数:cv.dft(),cv.idft()等 理论 傅立叶变 ...
- OpenCV-Python 形态学转换 | 十七
目标 在这一章当中, 我们将学习不同的形态学操作,例如侵蚀,膨胀,开运算,闭运算等. 我们将看到不同的功能,例如:cv.erode(),cv.dilate(), cv.morphologyEx()等. ...
- [React]Hook初探
Hook是什么 Hook是React从16.8开始支持的特性,使用Hook可以在不使用class时使用state Hook支持在不需要修改组件状态的情况下复用逻辑状态,从而解决使用render pro ...
- 数据库(sqlserver 2005)优化排查之路
查找问题过程是痛苦的,解决完问题是快乐! 兄弟帮助一个公司开发了一个旅游网站(asp.net+sqlsever2005),一直还算稳定,但是最近网站却慢的可以,让人头疼.登录服务器,进入任务管理器,发 ...
- Java中性能优化的45个细节
在JAVA程序中,性能问题的大部分原因并不在于JAVA语言,而是程序本身.养成良好的编码习惯非常重要,能够显著地提升程序性能. 1. 尽量在合适的场合使用单例 使用单例可以减轻加载的负担,缩短加载的时 ...
- [noip模拟]水灾<BFS>
水灾(sliker.cpp/c/pas) 1000MS 64MB 大雨应经下了几天雨,却还是没有停的样子.土豪CCY刚从外地赚完1e元回来,知道不久除了自己别墅,其他的地方都将会被洪水淹没. CCY ...
- Pytest系列(4) - fixture的详细使用
如果你还想从头学起Pytest,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1690628.html 前言 前面一篇讲了setup.te ...