两阶段认知论:
初级阶段:通过比喻建立直观认知(快速上手)
高级阶段:回归技术本质(精准掌握)
比喻只是辅助性的,最终需要去比喻化,专业术语是不可替代的。
从比喻认知到本质掌握,最终实现专业术语的自然理解与运用。
AQS
比喻理解
想象 AQS 就像一个茅坑:
茅坑(共享资源):每次只允许一个人上茅坑
排队(CLH队列):排队上茅房的人
茅坑状态(state状态):0为没人使用,1为有人使用
通知唤醒:别每次都问我上完了没,我上完了通知你,你再上
公平性:前面还有人排队,那第一个人先上;新来的人看见有人在排队,那他也去排队
共享模式无非是有多个茅坑,但是还是有数量限制,需要管制使用。
概念
针对同步状态管理这个需求,
把解决思路(模版)给你提供好,你根据自己的具体需要去具体实现。
解决多个线程对共享资源访问的同步问题。
AbstractQueuedSynchronizer,抽象队列同步器,用来构建锁和同步器。
ReentrantLock、Semaphore、ReentrantReadWriteLock、SychronousQueue、FutureTask、CountDownLatch等都是基于AQS。
三个要点:
1.把线程封装成Node(加上双向指针),存入阻塞链表队列(CLH)中。
2.利用一个volatile int state来控制同步状态和可重入,为1时想要获取锁的线程就加入到阻塞队列中,这个通过CAS完成。
3.唤醒操作
AQS的Node资源有两种共享模式,或者说两种同步方式:
- 独占模式(Exclusive):资源是独占的,一次只能有一个线程获取。如 ReentrantLock。
- 共享模式(Share):同时可以被多个线程获取,具体的资源个数可以通过参数指定。如Semaphore、CountDownLatch。
ReentrantLock的非公平锁实现:
- 线程1获取锁(acquire())->state = 1,
- 线程2来了,想CAS把state置为1,但是已经是1了,失败
- 线程2去看看当前的执行线程是不是自己(可重入)
- 是自己,state++
- 不是自己,接到阻塞队列中的头结点后面
- 线程2再去CAS企图修改state一次,失败,挂起了
- 线程1执行完毕,检查是自己的锁,把锁给释放了(release()),state置为0了,并且唤起阻塞队列head的下一个节点
- 阻塞对列中原来的head->null,让gc去回收,线程2升级为head,并且把value置为null
- 非公平性体现:线程1在唤起线程2的过程中,线程3来并且修改了state
公平性实现差异:head的下一个线程去获取锁 + 线程3在加锁的时候需要先判断头结点的下一个是否存在非当前线程的线程,若存在的话要让它先去执行。