- 1·.aqs是什么?
- 抽象的队列式的同步器,定义了一套多线程访问并发资源的同步框架,是基础
- 是java提供的底层的同步工具类,用一个int类型的变量标识同步状态,并提供一系列cas操作来关联同步专题
- 2.aqs作用是什么?
- 是基础概念,是底层支持,为java中的并发组件提供统一的底层支持,java中的reentrantlock和countdownlatch都是继承aqs实现的
- 3.aqs如何实现的?
- violate维护一个int类型的state
- 内部实现一个FIFO的同步队列,遵循先进先出原则,当第一个线程请求过来时候尝试获取锁时候,获取到了,就放到队列的头部;后续的线程再来请求锁就会封装成node节点放到队列后面,一直到最后一个;当第一个持有锁的线程执行完毕后释放锁,通知后面的节点去持有锁
state访问的三种方式:
- getState方法
- setState
- compareAndSetState方法
- 4.常见子类使用介绍:aqs定义两种对state的访问,独占(reentrantlock)和共享(信号量和countdownlatch),底层关于线程队列相关aqs已经自己实现好了,各个子类只需要继承并实现对状态的获取和释放即可
- 自定义同步器实现的几个常见方法:
isHeldExclusively 是否独占方法,只有condition用到 tryAcquire 独占,成功返回true,失败false tryRelease 独占,尝试释放,成功true,失败false tryAcquireShared 共享,尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 tryAcquireReleased 共享,尝试释放资源,如果释放后运行唤醒后续节点返回true,否则false - reenttrantlock:和可重入锁的实现方式差不多,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
- countDownlatch:任务分N个子线程执行,对应state = N,state和线程数量对应,每个子线程执行完后调用countdown进行CAS-1,直到所有任务执行完毕state = 0,这时候unpark调用主线程,最终acquire返回执行后续
- 一般自定义同步器要买独占要么共享,也有都实现的,ReentrantReadWriteLock
- 5.分析具体实现
- 独占型acquire(int)独占型顶级入口,如果获取到资源,就直接返回,没有就放到队列中,并且忽略中断的影响。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}tryAcquire:尝试获取线程
addWaiter(Node.EXCLUSIVE)将线程放到队列尾部并标记独享模式
acquireQueued:使线程再等待中获取资源,一直到获取到返回,如果中断过,返回true,否则返回false,如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。
关于独占型的acquire和release,主要记住主要流程即可,主体的tryAcquire流程记住
- 共享型具体实现:
tryAcquireShared:仍然需要共享型同步器去自定义实现,但是基础语义已经设定好,获取成功且无资源返回0,有剩余资源返回>0,失败返回<0;
与独占实现的两个不同:
- 1.只不过这里将补中断的selfInterrupt()放到doAcquireShared()里了,而独占模式是放到acquireQueued()之外;
- 2.共享的同步器只有当线程处于第二个时候才会去尝试获取资源,而独占没有这个现实,毕竟共享式的会有多个线程等待,要保证一下公平性
- 3.当当前线程执行完毕后,共享线程需要看当前剩余资源情况来确定是否需要通知下一个资源,假如出现老大用完后释放了5个资源,而老二需要6个,老三需要1个,老四需要2个。老大先唤醒老二,老二一看资源不够,他是把资源让给老三呢,还是不让?答案是否定的!老二会继续park()等待其他线程释放资源,也更不会去唤醒老三和老四了。独占模式,同一时刻只有一个线程去执行,这样做未尝不可;但共享模式下,多个线程是可以同时执行的,现在因为老二的资源需求量大,而把后面量小的老三和老四也都卡住了。当然,这并不是问题,只是AQS保证严格按照入队顺序唤醒罢了(保证公平,但降低了并发)
- tryReleaseShared:与独占的不同:
- 1.独占基于可重入考虑,需要state == 0 才释放并通知下一节点;而共享模式下的releaseShared()则没有这种要求,共享模式实质就是控制一定量的线程并发执行,那么拥有资源的线程在释放掉部分资源时就可以唤醒后继等待结点
补充:
不管是独享还是共享同步器,都是在获取到资源后才去将当前结点设置为队列的头结点,获取成功后还会将之前的头结点踢出去, help GC;release操作释放后不操作头结点
本文是读取文章
https://www.cnblogs.com/zyrblog/p/9866140.html
的观后感,感谢这位兄弟,不错