🚀 优质资源分享 🚀
学习路线指引(点击解锁) | 知识定位 | 人群定位 |
---|---|---|
🧡 Python实战微信订餐小程序 🧡 | 进阶级 | 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。 |
💛Python量化交易实战💛 | 入门级 | 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统 |
目录* 1 疑点todo和解疑
- 2 AbstractQueuedSynchronizer学习总结
- 3 AQS 简介
- 4 AbstractQueuedSynchronizer源码分析
- 4.1 类的继承关系
- 4.2 类的常量/成员变量
- 4.3 静态内部类Node
- 4.4 构造函数
- 4.5 核心方法分析
- 4.5.1 核心方法概览
- 4.5.2 acquire()方法
- 4.5.3 addWaiter()方法
- 4.5.4 enq()方法
- 4.5.5 acquireQueue()方法
- 4.5.6 shouldParkAfterFailedAcquire()方法
- 4.5.7 parkAndCheckInterrupt()方法
- 4.5.8 cancelAcquire()方法
- 4.5.9 unparkSuccessor()方法
- 4.5.10 release()方法
- 4.5.11 acquireSharedInterruptibly()方法
- 4.5.12 doAcquireSharedInterruptibly()方法
- 4.5.13 setHeadAndPropagate()方法
- 4.5.14 doReleaseShared()方法
- 4.5.15 releaseShared()方法
- 5 取消节点移出链表分析
- 6 在shared模式中为什么需要PROPAGATE状态
1 疑点todo和解疑
- 共享资源,这里面哪个地方体现了资源?
同步状态变量:state就是那个共享资源(private volatile int state;) Lock类继承AQS类并定义lock()、unLock()的方法,表示获取锁和释放锁。多线程并发访问同一个lock实例,lock()方法会cas修改state变量,修改成功的线程获得锁,其他线程进入AQS队列等待。
- sync队列的head为什么要交替,能不能共用最开始的head?
没有必要!sync队列是双向链表结构,出队时,head交替方式,只需要修改head和head后继2个节点引用关系;固定head,就要修改head,head后继,以及head后继的后继 共3个节点。显然前者效率更高
- node.prev = pred = pred.prev; 没弄懂这行代码
- 为什么在cancelAquire的unParkSuccess中,能将node的后继节点unpark() 万一node前面还有signal节点呢?
不存在的,因为经过判断得出此时node就是head的后继。并且必须由这个取消节点node来唤醒后继,要不node线程结束后,就没有线程能够唤醒队列里的其他节点了。
- shouldParkAfterFailedAcquire有一个疑问,如果线程被unPark唤醒后,tryAcquire()失败了,那么线程会再次进入parkAndCheckInterrupt 再次park阻塞起来,那么谁来唤醒线程呢?
先说结果:由抢到锁的那个线程来唤醒!
上述的场景是存在的,例如在非公平锁模式中,B线程被A线程唤醒,A结束,B成为head,B去执行tryAcquire(),但此时C线程抢占到锁,B执行tryAcquire()没有拿到锁,再次park阻塞。C线程执行结束后将A唤醒
- shouldParkAfterFailedAcquire为什么一定要先判断或者修改前置节点状态改为SIGNAL:-1,才会park阻塞?
只有将前置节点状态改为SIGNAL,才能确保当前节点可以被前置unPark唤醒。也就是说阻塞自己前先保证一定能够被唤醒。因为代码中:
独占模式下,唤醒后继前先限制:h.waitStatus != 0
共享模式下,唤醒后继前先限制:h.waitStatus=SIGNAL
- 如何理解这里说的中断:acquire()函数以独占模式获取(资源),忽略中断,即线程在aquire过程中,中断此线程是无效的
表示本线程在获取资源期间,如果被其他线程中断,本线程不会因为中断而取消获取资源,只是将中断标记传递下去。
- 怎么理解独占模式和共享模式
When acquired in exclusive mode,
* attempted acquires by other threads cannot succeed. Shared mode
* acquires by multiple threads may (but need not) succeed. This class
* does not "understand" these differences except in the
* mechanical sense that when a shared mode acquire succeeds, the next
* waiting thread (if one exists) must also determine whether it can
* acquire as well. Threads waiting in the different modes share the
* same FIFO queue.
- 共享模式:允许多个线程同时获取资源;当一个节点的线程获取共享资源后,需要要通知后继共享节点的线程,也可以获取了。共享节点具有传播性,传播性的目的也是尽快通知其他等待的线程尽快获取锁。
- 独占模式: 只能够一个线程占有资源,其它尝试获取资源的线程将会进入到队列等待。
- 响应中断并终止:线程只要被中断就不会获取资源:两种情况的中断:1、刚尝试获取、2、进入队列中等待,前者立即停止获取,后者执行取消逻辑,等待节点变为取消状态
- 共享模式,什么时候head状态变为PROPAGATE,这个状态值的影响是什么?
A、B先后进入队列,状态都是0。A获得资源,进入setHeadAndPropagate晋升为head,A进入doReleaseShared尝试唤醒B时,但B还没将A改为signal,因为A还是0,A将状态改为PROPAGATE
2 AbstractQueuedSynchronizer学习总结
2.1 AQS要点总结
对于AbstractQueuedSynchronizer的分析,最核心的就是sync queue的分析。
- 每一个节点都是由前一个节点唤醒
- 当节点发现前驱节点是head并且尝试获取成功,则会轮到该线程运行。
- condition queue中的节点向sync queue中转移是通过signal操作完成的。
- SIGNAL,表示后面的节点需要运行。
- PROPAGATE:就是为了避免线程无法会唤醒的窘境。因为共享锁会有很多线程获取到锁或者释放锁,所以有些方法是并发执行的,就会产生很多中间状态,而PROPAGATE就是为了让这些中间状态不影响程序的正常运行。
2.2 细节分析
2.2.1 插入节点时先更新prev再更新前驱next
//addWaiter():
node.prev = pred; // 1 更新node节点的prev域
if (compareAndSetTail(pred, node)) {
pred.next = node; //2 更新node前驱的next域
return node;
}
//enq():
node.prev = t; // 1 更新node节点的prev域
if (compareAndSetTail(t, node)) {
t.next = node;//2 更新node前驱的next域
return t;
}
//unparkSuccessor():
Node s = node.next; //通过.next来直接获取到节点的后继节点,这个节点的后继的prev一定指向节点本身
//....
if (s != null)
LockSupport.unpark(s.thread);
- addWaiter() 或者enq()插入节点时,都是先更新节点的prev域,再更新它前驱的next域。那么通过node.next()取到的后继,后继的prev域一定是指向node本身。如果先更新next域,在更新prev域时出现异常,那么通过.next取到不是完整的节点
- unparkSuccessor()唤醒后继时,Node s = node.next; 通过.n