AQS具备特征
设计思想
通过伪代码做分析
假设有3个线程t0,t1,t2
t0获取锁,
t1,t2加锁:
自旋锁
while(true) {
将无法获取到锁的线程t1加入队列中
LinedQueue.put(Thread1);
阻塞
LockSupport.part();
}
t0执行自己的业务
xxxx业务
xxx业务
t0执行完了
解锁:
t1出队列
Thread t = LinkedQueued.take();
解除阻塞
LockSupport.unpart(t1)
公平/非公平
synchronized不具备公平和非公平性
如何实现的?通过源码分析
ReentrantLock lock = new ReentrantLock(true);//其中false为非公平锁,true为公平锁。
什么是非公平锁和公平锁?
默认是false代表new NonfairSync(),true代表new FairSync();
其实发现NonfairSync()和FairSync()都继承于Sync,Sync是ReentrantLock内部类,该类继承AbstractQueuedSynchronized。其实这就是设计模式中的模板模式-子类根据需要做具体业务实现
AQS类内部属性
几个重要:
state:资源的可用状态
exclusiveOwnerThread:当前那个线程在用这个资源
waitStatus:
CANCELLED(1):表示当前结点已取消调度。当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。
SIGNAL(-1):表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为SIGNAL。
CONDITION(-2):表示结点等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。
PROPAGATE(-3):共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。
0:新结点入队时的默认状态。
注意,负值表示结点处于有效等待状态,而正值表示结点已被取消。所以源码中很多地方用>0、<0来判断结点的状态是否正常。
isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
通过源码分析lock中的aqs实现逻辑
以公平锁为例子
传入参数为1,主要是后期用在state这个变量上。
tryAcquire:尝试获取资源
addWaiter:添加等待队列
acquireQueued:自旋
1.获取当前线程
2.跟state做判断,0表示可以去获取资源,1表示无法获取资源
3.这个地方也挺有意思的,有两种情况可以获取到资源,一种是我们常见的state=0,另一种是当前线程还能再次获取到该资源,他会给state不断累加1
其实就是前面提到的双向循环链表,往这里面插入新的节点
1.为当前线程创建节点
2.将其插入到双向循环链表中。
注意:compareAndSwapObject其实CAS系列方法,通过源码看,他不是java实现的,是底层硬件提供支持的,只需4个参数(但是我们上篇讲的3个参数)why?其这四个参数是
obj :包含要修改的字段对象;
offset :字段在对象内的偏移量;
expect : 字段的期望值;
update :如果该字段的值等于字段的期望值,用于更新字段的新值;
前两指的是内存中的变量值
其实这里就类似于:物理地址(内存中真正的地址)和逻辑地址(我们程序中用到的地址,他是由两部分组成的段地址+偏移地址)
已在队列中的线程,准备阻塞等待获取锁。
该节点前驱节点 必须是头节点才有机会tryAcquire,其他节点没有机会,如果获得该机会,而且tryAcquire也获取到资源了,该节点去执行自己任务,并且把该节点置换为头节点;如果该节点没有机会,也就是其前驱不是Head,通过它判断shouldParkAfterFailedAcquire是否应该阻塞,当前面条件成立后,也就是waitStatus = -1,当前线程才会被阻塞线程,且允许中断操作。