我们今天讲一下AQS在ReentrantLock加锁解锁过程中的作用,首先简单的说一下AQS中一个重要类(AQS的内部类)Node类,顾名思义这个类是一个节点类,个人觉得这个类中有三个属性比较重要(其他的没怎么用到,我也不太知道啥作用~~)。
volatile int waitStatus;
这个属性表示节点的等待状态(按照英文直译),他包含的状态有几种(源码中包含的):
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
在我们的后面的源码分析中大家要记住两个状态0和-1;0为初始化的的状态,-1表示这个节点挂起在等待唤醒。
volatile Thread thread;
这属性表示某个线程对应的节点,举例:t1线程对应的节点中包含的thread=t1;
private volatile int state;
这个属性表示同步的标志,如果state>0表示现在“锁”被占用着。为什么会有大于1的情况呢下面源码分析会进行说明
源码分析:
1、ReentrantLock r=new ReentrantLock(true);r.lock();
true表示公平锁
公平锁和非公平锁的区别:
在锁没有被占用时,公平锁会首先判断队列中是否有节点(node,判断t==h,如果相等的话则则说明没有node),有节点则进入队列排队,没有的话才进行锁竞争
在锁没有被占用时,不公平锁会直接去竞争锁,失败后在进入队列排队
java.util.concurrent.locks.AbstractQueuedSynchronizer.state–>锁标志 0表示没有被占用 1,2,3表示已加锁
公平锁:
!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
只有tryAcquire=false才可以进入acquireQueued(进入队列操作)
tryAcquire(1)–>锁获取成功的只有两种方式:
一、锁状态为0 且 队列中没有node(或者head后的节点中的线程就是现在的线程)且能加锁成功
getState() ==
0&&!hasQueuedPredecessors() &&compareAndSetState(0, acquires)–>true:getState() ==
0&hasQueuedPredecessors()==
false &&compareAndSetState(0, acquires)
hasQueuedPredecessors()–>h != t &&((s = h.next) == null || s.thread != Thread.currentThread())–>队列中没有node(或者head后的节点中的线程就是现在的线程)
二、现在只执行的线程和我需要竞争的锁是同一个锁 ,这样就会进行锁的重入
current == getExclusiveOwnerThread() int nextc = c + acquires; 这边会对sate进行加一操作 这边就可以很明白的说明为什么lock和unlock要成对出现
第一次进入不会对链表进行操作
acquireQueued(addWaiter(Node.EXCLUSIVE), arg) -->开始进行了链表操作
java.util.concurrent.locks.AbstractQueuedSynchronizer.Node.nextWaiter 等待队列中的后继节点
addWaiter(Node.EXCLUSIVE)–>增加将这个线程对应的node加入到链表中 nextWaite=Node.EXCLUSIVE
这边有两种情况:
acquireQueued(Node,int)–> 会进行死循环 在死循环中进行两个判断p ==
head && tryAcquire(arg)和shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()
p–>为当前节点的前一个节点
p == head && tryAcquire(arg)–> 如果前一个节点是head节点则进行获取锁的尝试 (这个和上面的一样)
如果成功的话这将这个节点设置为head节点 thread这是为null
shouldParkAfterFailedAcquire(p, node)–>获取前一个节点的waitStatus 如果是-1这返回true 如果大于0的话则对前面的节点进行删除知道碰到一个waitStatus不大于0的节点 如果是其他(>-1或者=0)的时候将前一个节点的waitStatus设置为-1
这个的目的只是将这个节点前面waitStatus>0的节点给删除
parkAndCheckInterrupt()–>将当前线程挂起 挂起后就不会执行循环了
LockSupport.park(this)–>UNSAFE.park(false, 0L) LockSupport.park(); 这个是对当前线程进行了挂起
出现异常
cancelAcquire(node)–>
node.thread = null;设置自身的线程为空
node.waitStatus = Node.CANCELLED设置自身的waitstatus为-1
一、如果node为tail则把前面一个node设为tail 删除自己
二、node不为tail
pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||(ws <= 0 && compareAndSetWaitStatus(pred, ws,Node.SIGNAL))) &&
pred.thread != null true:node前一个节点是不是head且(前一个节点的waitstatus为-1或者小于0时能设置为-1且前一个节点的Thread不为空 也就是前一个节点在等待状态 waitstatus为-1,-2,-3
true:如果这个节点下下一个节点的waistatus为-1,-2,-3的话 就将这个节点的下一个节点设置这个节点的前一个节点的next 也就是排除这个节点
false:这个节点的waitstatus<0就设置为0 如果这个节点的下一个节点为空或者下一个节点的waitstatus>0则设置下一个节点为null然后从尾节点开始先前找找到第一个waitstatus<=0的节点
最后只要这个节点不为空就进行 LockSupport.unpark(s.thread); (一种是第一个waitstatus<=0的节点一个就是他后面不为null的节点)
2、ReentrantLock r=new ReentrantLock(false);r.lock();
非公平锁一开始就会进行锁竞争compareAndSetState(0, 1) 竞争失败才会走acquire(1)
和公平锁不一样的地方在于对tryAcquire(1)的复写
他锁状态为0直接去竞争锁(公平锁是去判断链表中是否有节点)
其他的流程和公平锁一样
selfInterrupt()进行打断操作
3、ReentrantLock r=new ReentrantLock(false);r.unlock()
解锁的时候不会区分公平锁和非公平锁
sync.release(1);–>tryRelease(1)–>java.util.concurrent.locks.ReentrantLock.Sync.tryRelease(1)
int c=getState() - 1;锁状态减一
必须保证现在的线程和现在占有锁的线程是一个 否则抛出异常
如果c=0 说明不是重入锁只要解锁一次就释放锁了 并将占有锁的线程设置为null
将c设置为锁状态
head != null && head.waitStatus != 0 head后面有节点则head的waitstatus!=0 waitstatus为-1
为什么判断为waitstatus!=0 这个是这样考虑的 我们在唤醒一个节点后会将这个节点替换原来的head节点,按照我们直接的考虑只有最后一个节点的waitstatus=0的其他的时候都不为0(可能为-1,-2等)
unparkSuccessor(h);–>java.util.concurrent.locks.AbstractQueuedSynchronizer.unparkSuccessor(head)
获取head的waitstatus如果waitstatus<0则设置为0
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}表示找到head后面第一个不为空,且waitstatus<=0的节点
如果这个节点不为空则对他进行唤醒操作
加锁和释放所对应的节点分几种情况进行分析:
1、只有一个线程竞争锁(直接获取锁)
head=tail=null 直接获取到锁
2、两个线程竞争锁(一个线程获取到锁,一个进入链表)
3、三个及以上线程竞争锁(一个线程获取到锁,二个线程进入链表)