AQS源码解读
2021/7/26 22:35:45
本文主要是介绍AQS源码解读,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
抽象队列同步器(Abstract Queued Synchronizer,AQS)作为并发包JUL中一个基础组件,用来实现各种锁和同步组件,AQS主要由状态state变量、加锁线程和等待队列组成。AQS定义了多线程访问共享资源的框架,AQS定义了Exclusive(Reentrantlock)和share(Semaphore和CountDownLacth)两种资源共享方式,在不同的组件中都有基于AQS的自定义同步器,不同的自定义同步器主要区别在获取state的方式不同。
AQS内通过一个FIFO队列来管理资源获取线程的排队工作,并通过一个int型的变量来表示线程持有锁的状态,通过CAS锁对int变量进行修改。int变量实现线程之间互斥,state等于1表示共享资源已经被线程加锁;state等于0,表示共享资源没有被人加锁;state大于1表示共享资源已经被同一个线程多次加锁。下图为AQS和其它类之间的一个集成依赖关系。
1.Reentranlock整体加锁过程
第一个过程是ReentranLock整体的加锁过程,这个过程主要描述了一个新任务的执行时机和新任务入队时机。
ReentrantLock的实现过程,在ReentrantLock通过集成AQS事项了公平了非公平的同步器,无参方法创建ReentrantLock时创建,内部调用的是非公平同步器。
public void lock() { //调用非公平阻塞的lock方法 sync.lock(); } final void lock() { //state初试值为0,表示第一个线程 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1);// 不是第一个线程 }
2.任务入队过程
第二个过程是任务阻塞入队的过程,在真正阻塞之前,进入到任务等候队列,会两次尝试去获得资源锁,两次都获取不到,任务线程才会进入阻塞状态,等待前面的线程执行完毕。线程使用了LockSupport的自旋锁进行线程阻塞。
无法获得共享资源锁的线程会被封装在一个Node中,被阻塞后放在任务队列中等待被唤醒。Node中有一个表示线程状态的标志位waitStatus和记录阻塞线程的Thread变量。
Node中waitStatus的值为:
0:创建一个node的默认值。
CANCELLED(1):表示线程获取锁的请求已经被取消。
CONDITION(-2):表示节点在等待队列中,节点线程等待被唤醒。
PROPAGATE(-3):当先线程处于SHARED情况下,该字段才会被使用。
SIGNAL(-1):表示线程已经准备好了,等待资源被释放了。
最终任务队列中节点状态为:
1) 除了第一个节点,所有节点都处于阻塞状态;
2) 除了尾节点,所有节点waitStatus==Node.SIGNAL。
3.Reentranlock释放锁过程
第三个过程是等待队列中的线程被唤醒的过程,主要描述了等待线程唤醒时机,唤醒线程从等待队列到执行所做的一些动作。
Q1:为什么在释放锁的过程中从队列尾往前找第一个WaitStatus不为CANCELLED的NODE节点进行释放。
Node s = node.next;//node为head节点 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) LockSupport.unpark(s.thread);
因为Node节点在入队的时候,保证了prev的强一致性,node的next的是弱一致性。维护一致性通常需要牺牲部分性能,为了进一步的提升性能,使用了弱一致性。
node.prev = pred;//无条件执行 if (compareAndSetTail(pred, node)) { pred.next = node;//if满足才能执行 return node; }
Q2:AQS为什么使用双向队列作为任务等待队列?
AQS使用单向队列也能实现任务队列,为什么还要使用到双向队列,通过head指针和tail指针就可以方便节点的移除和增加,但是在很多场合,对当前节点的操作,需要知道前一个节点的状态,为了节省访问上一个节点的时间复杂度,使用了双向队列,查询当前节点上一个节点的时间复杂度为O(1)。
例如: final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor();//需要获取前驱节点信息 ... }
Reference
[1] https://www.bilibili.com/video/BV1Hy4y1B78T
[2]https://monkeysayhi.github.io/2017/12/05/%E6%BA%90%E7%A0%81%7C%E5%B9%B6%E5%8F%91%E4%B8%80%E6%9E%9D%E8%8A%B1%E4%B9%8BReentrantLock%E4%B8%8EAQS%EF%BC%881%EF%BC%89%EF%BC%9Alock%E3%80%81unlock/.
这篇关于AQS源码解读的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-05并查集详解与实现教程
- 2024-11-05大厂数据结构与算法入门指南
- 2024-11-05大厂算法与数据结构入门指南
- 2024-11-05二叉树入门教程:轻松掌握基础概念与操作
- 2024-11-05红黑树入门教程:从零开始理解红黑树
- 2024-11-05初学者必备:链表基础知识详解
- 2024-11-05平衡树入门教程:理解、构建与应用
- 2024-11-05数据结构入门教程:轻松掌握基础知识
- 2024-11-05数据结构与算法入门教程
- 2024-11-05优先队列入门教程:理解与实现