概述
文章目录
- 介绍
- doc文档说明
- 方法
- 获取排他锁流程
- 释放排它锁流程
- 获取共享锁流程
- ConditionObject
- await方法
- singal方法
- jdk实现类
- CountDownLatch(共享锁)
- Semaphore(共享锁)
- ThreadPoolExecutor.Worker(排它锁)
- ReentrantLock(排它锁)
- ReentrantReadWriteLock(排它锁和共享锁)
- WriteLock(排它锁、可重入)
- ReadLock(共享锁)
介绍
基于JDK14的源码进行解析,需要看过源码后,再来理解本文会简单很多。
doc文档说明
AQS类提供了框架来实现阻塞锁和相关的同步器FIFO等待队列,用AtomicInteger代替state,继承该类必须保护好state,state代表的是获取锁(acquire)或释放锁(release)。其他的方法都是入队列和阻塞机制。
Node结点有三种状态:
WAITING
= 1;
// must be 1
CANCELLED = 0x80000000; // must be negative
COND
= 2;
// in a condition wait
处于head结点的,是持有资源的线程。
方法
JDK14流程
enqueue方法:for循环 + cas的方式,入队列。
isEnqueued方法:从链表尾部查找node,若有返回true。
获取排他锁流程
acquire方法:先调用tryAcquire获取锁,若返回false,则调用acquire(私有)方法。
acquire(私有)方法:
重复干如下事情:
- 检查当前的前继结点是否是头结点,如果是,确保头结点稳定,否则确保有效的前继结点
- 如果前继结点是头结点,或尚未加入队列,则尝试获取锁,若获取锁成功则结束
- 如果结点尚未创建,则创建它;否则,如果结点尚未入队,请尝试一次入队;否则从park中唤醒,重试(到postSpin时间);否则,如果等待状态未设置,则设置并重试;否则暂停当前并清除等待状态,并检查取消;
若超时,则调用cancelAcquire取消当前结点。
cancelAcquire方法:设置当前node状态为WAITING状态,调用cleanQueue方法。
cleanQueue方法:清空所有状态为CANCELLED的node结点,并调用signalNext方法尝试unpark下一个结点。
signalNext方法:唤醒该结点的后继结点,若后继结点状态不等于0,则unpark,它从acquire(私有)方法里面执行清空等待状态然后重复去申请一次资源看是否能成功。不用担心后继结点为CANCELLED状态,因为后续发现这个状态会调用cleanQueue方法清空所有状态为CANCELLED的node结点。而且CANCELLED状态只有线程持有state时候,才回去调用cleanQueue,所以不用考虑并发。
释放排它锁流程
release方法:调用自定义的tryRelease释放资源,若为true,则调用signalNext方法唤醒该结点的后继结点。
获取共享锁流程
和排它锁类似,只要tryAcquireShared(arg) >= 0就能拿到共享锁。
signalNextIfShared方法:signalNext方法类似,只是多了个判断是否是共享锁对象的条件。
ConditionObject
该类需要在AQS中使用,类似于wait,sleep,该类维护着一个单向链表ConditionNode。只用了排它锁。
await方法
创建一个ConditionNode结点,若当前结点的等待线程是该锁,设置状态为00000011(COND | WAITING
),添加到链表中,释放当前锁。若当前线程已经有结点进入等待队列了,等待队列中结点释放,然后清空状态为0,入等待队列。
singal方法
遍历ConditionObject链表,若遍历时对应的结点的状态是3,即00000011(代表着该结点已经处于等待状态),通过cas方式入等待队列。
jdk实现类
CountDownLatch(共享锁)
构造函数中先设置初始状态为入参。
- 获取锁的时候,state减一,直到为0。
- 释放锁的时候,判断state是否为0,为0才允许释放。
使用场景例子: 一个任务的某个步骤需要很多小任务,主任务就相当于主线程,分出来各个小任务给其他线程,就可以使用CountDownLatch等待所有的小任务都执行完毕,主任务再往下执行。
Semaphore(共享锁)
构造函数中先设置初始状态为入参。有着公平锁和非公平锁实现类。
公平锁:直接先去竞争state,竞争失败入队列。
非公平锁:如果有前驱结点在队列中,直接入队列。
使用场景:Semaphore可以用于做流量控制,特别公用资源有限的应用场景,比如数据库连接。假如有一个需求,要读取几万个文件的数据,因为都是IO密集型任务,我们可以启动几十个线程并发的读取,但是如果读到内存后,还需要存储到数据库中,而数据库的连接数只有10个,这时我们必须控制只有十个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。这个时候,我们就可以使用Semaphore来做流控。
ThreadPoolExecutor.Worker(排它锁)
一个Worker线程对应一个等待队列。
ReentrantLock(排它锁)
前置条件,可重入锁,若发现持有者和当前线程一致,state++,然后执行公平锁和非公平锁的逻辑。
公平锁:直接先去竞争state,竞争失败入队列。
非公平锁:如果有前驱结点在队列中,直接入队列。
ReentrantReadWriteLock(排它锁和共享锁)
公平锁:直接先去竞争state,竞争失败入队列。
非公平锁:如果有前驱结点在队列中,直接入队列。
使用的是同一个等待队列,共用一个state。
WriteLock(排它锁、可重入)
获取锁流程:
- 如果state不为0
- state和65535与运算等于0,进入等待队列。
- 当前线程和持有线程不一致,进入等待队列。
- 如果state + acquires大于65535,抛出异常。
- 以上都没有,state设置为state + acquires,继续持有该锁。
- 否则说明当前没有资源的竞争
- 如果是非公平锁,直接获取锁,若成功持有锁,若失败则进入等待队列。
- 如果是公平锁且有前驱结点在队列中,直接入队列;否则持有锁。
释放锁流程,和前面大体一致。
ReadLock(共享锁)
维护着一个HoldCounter的ThreadLocal,用于记录每个线程所重入的次数。
获取锁流程:
- 如果有WriteLock锁且持有锁者与当前线程不一致,则进入等待队列。
- 调用~readerShouldBlock方法,
- 如果是非公平锁且head的后继结点不为共享锁,继续流程。
- 如果是公平锁且有没有前驱结点在队列中,继续流程。
- HoldCounter++,如果是第一个读线程,记录该结点。
- 继续持有锁,结束流程。
- 如果有WriteLock锁且持有锁者与当前线程不一致,则进入等待队列。
- 调用readerShouldBlock方法
- 如果是非公平锁且head的后继结点不为共享锁,继续流程。
- 如果是公平锁且有没有前驱结点在队列中,继续流程。
- 如果第一个读线程是当前线程,继续流程。
- 如果当前读线程所重入次数==0,进入等待队列,结束流程。
- HoldCounter++,如果是第一个读线程,记录该结点。
释放锁流程,HoldCounter–,如果是第一个读线程,清除该结点,等待state == 0时,才会释放所有读线程。
最后
以上就是等待溪流为你收集整理的JDK14的AbstractQueuedSynchronizer(AQS)源码解析介绍doc文档说明方法ConditionObjectjdk实现类的全部内容,希望文章能够帮你解决JDK14的AbstractQueuedSynchronizer(AQS)源码解析介绍doc文档说明方法ConditionObjectjdk实现类所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复