About AQS——有关AQS与其实现的同步器,自问自答系列

1. 定义:什么是AQS?

抽象的队列同步器,依赖于volatile修饰的同步状态state和一个FIFO的同步队列,是锁和其他的同步器的底层实现框架。

2. 作用:java中已有synchronized关键字,为什么需要AQS?

  1. 在jdk1.5之前,synchronized都是重量级锁,其性能较低,之后才做出了偏向锁、轻量级锁的优化
  2. 响应中断:synchronized一旦进入阻塞状态,就无法被中断
  3. 支持超时:线程再一段时间之内没有获取到锁,不再进入阻塞状态,而是返回错误

3. 实现:同步队列中的节点状态?

  • CANCELLED(1):表示当前结点已取消调度。因为超时或者中断,结点会被设置为取消状态,进入该状态后的结点将不会再变化。注意,只有 CANCELLED 是正值,因此正值表示结点已被取消,而负值表示有效等待状态。
  • SIGNAL(-1):表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为 SIGNAL。
  • CONDITION(-2):表示结点等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。
  • PROPAGATE(-3):共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。
  • INITIAL(0):新结点入队时的默认状态。

说明: 大致可以分为两种场景,Lock 和 Condition 两种。

Lock 对应的状态变化:

  • lock.lock() 获取锁时:
    • 如果线程能获取到锁,就不会进行等待队列,进而也就不会有之后的各种状态变化。
    • 如果不能获取到锁,此时就会进行同步队列(syncQueue),进行同步队列后会前其驱结点的状态改为 SIGNAL。注意,是修改前驱结点的状态为 SIGNAL,表示需要唤醒后继结点。
    • 当然,在其前驱结点的状态改为 SIGNAL 前,线程可能就被中断、超时、唤醒。此时,会直接修改当前结点的状态为 CANCELLED。
  • lock.unlock() 释放锁时:
    • 释放锁时,需要通过 unparkSuccessor 方法唤醒后继结点。唤醒后继结点后,会将 head 指针移动到该后继结点,也就删除头结点。

Condition 对应的状态变化:

  • condition.await 进行等待队列时:首先,线程会释放锁并唤醒后继结点。然后,将当前线程进入到等待队列(watiQueue)中,同时结点的状态变成 CONDITION。
  • condition.signal 唤醒等待线程:首先,将结点的状态修改为 INITIAL,如果失败则说明结点已经取消,不需要处理,继续轮询下一个结点。然后,将该结点的前驱节点状态修改为 SIGNAL,否则直接唤醒该线程。

锁原理 - AQS 源码分析:有了 synchronized 为什么还要重复造轮子

4. 实现:公平锁与非公平锁?

  • 公平锁 FairSync
    公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁;
    公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU 唤醒阻塞线程的开销比非公平锁大。
  • 非公平锁 NonfairSync
    非公平锁是多个线程加锁时直接尝试一次获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁;
    非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU 不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。

5. 实现:共享锁和独占锁?

  • 独占锁:
    独占锁是只有头节点获取锁,其余节点的线程继续等待,等待锁被释放后,才会唤醒下一个节点的线程;
    有现场获取锁后,同步状态state值先从0到1切换,如果同时是可重入的,那么同一个线程可以再将state++
  • 共享锁:
    共享锁是只要头节点获取锁成功,就在唤醒自身节点对应的线程的同时,继续唤醒AQS队列中的下一个节点的线程,每个节点在唤醒自身的同时还会唤醒下一个节点对应的线程,以实现共享状态的“向后传播”,从而实现共享功能。因此,在共享锁模式下,在获取锁和释放锁结束时,都会唤醒后继节点

6. 根据代码来看共享、独占模式下的锁获取与释放

锁获取锁释放
独占模式在这里插入图片描述1. tryAcquire首先尝试获取锁(这个方法是在自定义同步器中实现的)
2. 如果成功获取,则方法结束
3. 获取锁不成功:
3-1.线程加入同步队列
3-2.线程获取锁出队
在这里插入图片描述1. tryRelease首先尝试释放锁(这个方法是在自定义同步器中实现的)
2. 如果释放失败,返回false;
3. 如果释放成功,唤醒后续一个节点;
共享模式在这里插入图片描述1. 首先尝试获取锁
2. 如果获取锁成功,则方法结束
3. 获取锁不成功:
3-1.线程加入同步队列
3-2.线程获取锁出队,继续唤醒后面的节点,实现"共享传递"
在这里插入图片描述1.tryRelease首先尝试释放锁(这个方法是在自定义同步器中实现的)
2. 如果释放失败,返回false;
3. 如果释放成功,唤醒后续所有节点

7. 根据代码来看各个同步器实现的AQS接口逻辑

获取锁释放锁state代表含义
Reentrant
Lock
在这里插入图片描述
获取state锁数量、如果state=0,返回成功;
如果state!=0,再判断持有线程是否为当前线程
在这里插入图片描述
释放锁,将state–
记录当前锁数量
0:无锁状态;
1:有一个线程;
1+:同一个线程的重入写;
Reentrant
ReadWrite
Lock
中的读锁
在这里插入图片描述
分别获取state的读锁、写锁数量;根据逻辑判断是否能获取
在这里插入图片描述
释放锁,将state–
按位拆分,记录读锁、写锁数量
可能为:
0:无锁状态;
1:一个线程获取;
1+:多个线程读、或者一个线程的重入写;
CountDown
Latch
在这里插入图片描述
state = 0 ,表示倒数计数器已为0,返回成功
在这里插入图片描述
释放锁,将state –
倒数计数器当前的值
>0:表示有线程在阻塞;
0:倒数计数器已为0,将唤起所有线程
Semaphore在这里插入图片描述
如果 state - 1 >0,表示还有可用的凭证,返回成功
在这里插入图片描述state++剩余的可用凭证
>0:有剩余可用凭证;
0:已没有可用凭证
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值