//分析的是公平锁加锁过程....
//尝试去拿锁
public final void acquire(int arg) {
//如果没拿到锁 拿到锁返回true 注意这里取反
if (!tryAcquire(arg) &&
//当前线程加入到aqs 此时还没拿到锁就阻塞 里面还会判断自己是否是第一个排队
//尝试去拿锁 拿到锁失败时会去将当前节点的上一个节点的等待状态设置为-1 此时返回false
//再次尝试去拿锁(总共尝试两次去拿锁) 拿不到时会将当前线程阻塞 调用park()
//addWaiter()添加当前线程在aqs
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//添加等待的线程到aqs 维护aqs链表得关系
private Node addWaiter(Node mode) {
//初始化一个节点
Node node = new Node(Thread.currentThread(), mode);
//临时变量 假设tail(队尾=null)
Node pred = tail;
//队尾不等于null
if (pred != null) {
//当前节点的上一个节点是尾节点 插入到尾节点之后
node.prev = pred;
//采用cas将尾节点设置为当前节点
if (compareAndSetTail(pred, node)) {
//尾节点的下一个节点就是当前节点
pred.next = node;
//返回当前节点
return node;
}
}
//初始化第一个节点
enq(node);
//返回当前节点
return node;
}
//初始化第一个节点
private Node enq(final Node node) {
for (;;) {
//将aqs尾节点临时存放
Node t = tail;
if (t == null) { // Must initialize
//采用cas设置头节点 方便运算 头的线程永远是null
if (compareAndSetHead(new Node()))
//头尾相连 //代码走完 继续循环
tail = head;
} else {
//之前得尾节点赋值给当前得节点得上一个节点(也就是当前节点得上一个节点是之前得
//尾节点
node.prev = t;
//采用cas设置尾节点
if (compareAndSetTail(t, node)) {
//当前节点就是尾节点
t.next = node;
//返回当前节点 for循环结束
return t;
}
}
}
}
//node:排队得尾节点
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
//中断标识
boolean interrupted = false;
for (;;) {
//获取排队得尾节点的上一个节点
final Node p = node.predecessor();
//1.如果上一个节点是头节点 就是第一个排队 尝试去拿这把锁
//2.有可能当前正在拿锁的线程上一个节点持有锁的人已经执行完了
//并且释放了锁 而且当前节点刚好也拿
//到锁 此时的头节点就变为了当前节点 并将上一个节点持有锁的人从aqs中移出
if (p == head && tryAcquire(arg)) {
//设置头节点为当前节点
setHead(node);
//2.将之前的头节点的指向改为null 方便gc
p.next = null; // help GC
failed = false;
//返回中断状态 此时为false? ??
return interrupted;
}
//失败获取锁后要不要睡眠调用 目的是为了防止等待的线程去调用park()
if (shouldParkAfterFailedAcquire(p, node) &&
//阻塞调用park() 此时当前线程处于阻塞状态 有可能在阻塞时
//上一个拿到锁的执行完了 唤醒了此线程 这是中断状态将变为true
parkAndCheckInterrupt())
//设置中断状态为true
interrupted = true;
}
} finally {
//待分析
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//拿到当前节点的上一个节点的状态 waitStatus默认为0
int ws = pred.waitStatus;
//Node.SIGNAL=-1 如果上一个节点的状态为-1 也就是上一个节点处于等待拿锁 直接返回true
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
//返回true
return true;
//待分析
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//将当前排队的尾节点的上一个节点的等待状态修改为-1
//每次是当前线程进来排队时将上一个节点的等待状态修改为-1
//因为上一个节点处于等待阻塞状态 不能自己去修改自己
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
//返回flase
return false;
}
//失败获取锁后要不要睡眠调用park()睡眠 pred:当前队尾的上一个节点 node:当前队尾
//将当前线程阻塞 并等待被唤醒
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
protected final boolean tryAcquire(int acquires) {
//获得当前线程
final Thread current = Thread.currentThread();
//获取当前锁的状态 0未持有 1持有 这个属性在AbstractQueuedSynchronizer中
int c = getState();
if (c == 0) {
//判断是都需要排队
if (!hasQueuedPredecessors() &&
//采用cas将当前aqs队列中state设置为1
compareAndSetState(0, acquires)) {
//将当前线程赋值设置为正在占用锁的线程
setExclusiveOwnerThread(current);
//拿到锁 并返回
return true;
}
}
//如果当前进来的这把锁跟占有这把锁的线程是相等 证明是同一把锁 可重入锁
else if (current == getExclusiveOwnerThread()) {
//可重入锁次数+1
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//将重入锁数量设置给aqs的status
setState(nextc);
return true;
}
return false;
}
//判断当前线程是否需要排队
public final boolean hasQueuedPredecessors() {
//拿到尾节点
Node t = tail;
//拿到头节点
Node h = head;
Node s;
//第一次进来t跟h都为null 返回false 由于是!hasQueuedPredecessors()所以拿锁成功
//如果有人排队判断头节点的下一个节点是否等于null 显然不等于null所以为false
//由于是|| 所以进入下一个判断头节点的下一个节点线程是否不等于当前线程 这里保证每次拿锁
//的线程是头节点的下一个节点 如果不等于 显然返回true 那么当前线程将要去排队
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
-
总结: 尝试去拿锁 首先获取当前线程和当前锁的状态 如果是0 查看是否需要排队 - 不需要排队则直接拿锁成功 并使用cas将当前锁的状态修改为1 并设置持有这把锁的线程 返回true
- 还有一个判断就是:如果当前线程等于持有这把锁的线程 就是可重入锁 将锁的状态加1
-
- 需要排队则 去排队 将当前线程封装成Node插入aqs链表(第一个排队会创建一个线程为null的Node方便运算) 并返回
- 如果当前只有一个自己在排队(当前节点链表的上一个节点等于头节点)并且拿到锁(自旋拿锁) 则替换之前的头节点为
- 当前节点 并将之前头节点置空 方便回收
- 如果当前节点不是只有一个在排队 则去判断上一个节点的等待状态是否为-1 是-1则返回true 将当前线程调用park()阻塞
- 上一个节点的状态等于0的话 采用cas将上一个线程的等待状态设置为-1 并返回fasle 再次尝试去拿锁(自旋拿锁) ...所以 一般 会尝试两次去拿锁 这里会一直循环
- 如果当前不止一个在排队 则直接判断上一个节点的等待状态是否为-1 是-1则返回true 将当前线程调用park()阻塞
- 上一个节点的状态等于0的话 采用cas将上一个线程的等待状态设置为-1 并返回fasle 再次尝试去拿锁 这里会一直循环
- 释放锁的时候会将头节点的下一个节点等待状态为-1的唤醒 调用unpark() 唤醒之后就会走上面的cas+自旋+park