0. 我先梳理一下公平锁的流程,非公平锁是在公平锁的基础上多了两次抢锁的过程,也就是不公平的地方
公平锁上来就是 acquire(1)
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
**acquire() 方法里面调用了三个方法 :tryAcquire, addwaiter 和 acquireQueued **
tryAcquire会判断当前锁是否是自由的,即 state == 0? 如果是,那么就看队列是否有人在排队 hasQueuedPredecessors(),没有的话直接就CAS成功获取到锁了,或者是重入锁,否则失败的话就需要入队列,其实这里也有点讲究,分为三种情况,
- 线程T1是整个锁的第一个顾客
- 线程T2是整个锁的第二个顾客
- 线程T3及以上,是整锁的第三 、四、五 。。。个顾客
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
如果是第一个顾客T1,此时 tail == head == null ,没人初始化,所以 hasQueuedPredecessors返回false,然后CAS改状态并且设置OwnerThread,就return true 加锁成功,没有初始化队列,否则判断是否是重入,不然就失败 返回false;
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
- 如果tryAcquire(arg) 返回True,那么**!tryAcquire(arg)**
就是False,后面就不用走了,直接一路返回,加锁成功!所以有一个关键,第一个线程和队列无关,队列没有初始化它就返回了 - 返回false,说明获取锁失败,就来一次acquireQueued(addWaiter(Node.EXCLUSIVE), arg)),这里才是和队列相关的地方。设置完队列后 head!= tail。
- 初始化队列后,这里第二个顾客和第三及以上的顾客又有一个区别,就是如果pre节点是head,那么他就会再尝试获取锁,失败了就pack(),而第三个以上的节点直接放弃治疗了,直接pack();
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
- 对于非公平锁,一旦判断c == 0,即锁是自由的,直接就CAS改状态
公平锁:
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
非公平锁:
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
1. ReentrantLock非公平锁加锁流程
首先尝试CAS的方式将state从0改为1,改成功就将当前的线程所有者改为自己。这里体现了第一次不公平,一上来就CAS和改owner线程,而公平锁的话获取acquire(1)获取锁
final void lock() {
//调用CAS尝试获取锁
if (compareAndSetState(0, 1))
//尝试成功则修改当前拥有锁的线程是自己
setExclusiveOwnerThread(Thread.currentThread());
else
//获取失败
acquire(1);
}
如果失败,就调用 acquire(1)
public final void acquire(int arg) {
**//tryAcquire:尝试获取锁,加取反则表示获取失败后执行&&后面的方法
//addWaiter:将当前线程封装为Node后添加到同步队列尾部
//acquireQueued:线程入队后再次尝试获取锁或者阻塞**
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
2. 尝试获取锁 tryAcquire
尝试获取锁默认是获取非公平锁,首先看程序当前线程的状态getState(),如果是0,说明没人用,那么就CAS去改,改成功就行了,如果不是0,还要看看是否是自己当前已经拿着锁了,毕竟可重入,是的话state的数值就要再加,算是重入了吧,如果不是那就获取不到了,返回false
protected final boolean tryAcquire(int acquires) {
//非公平的尝试获取锁
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取当前锁状态
int c = getState();
//如果锁空闲
if (c == 0) {
//调用CAS尝试获取锁
if (compareAndSetState(0, acquires)) {
//尝试成功则修改当前拥有锁的线程是自己
setExclusiveOwnerThread(current);
return true;
}
}
//如果锁仍被持有而且持有锁的线程是自己
//这里体现了ReentrantLock的可重入性
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
//锁状态state不能为负值
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//修改state,不使用CAS是因为此时不可能有其他线程可以修改state
setState(nextc);
return true;
}
//获取锁失败
return false;
}
3. 将线程包装成node节点添加到队列
将线程包装成node节点,然后找到尾巴tail节点,如果tail不是null,则将node接在tail后,再用CAS的方式将tail指向node
如果tail是null,说明队列还没有初始化或者上面的CAS将tail指向node失败(其他线程已经改了),那么就调用enq(node);
private Node addWaiter(Node mode) {
//给当前线程新建Node结点,mode表示是排它锁还是共享锁
Node node = new Node(Thread.currentThread(), mode);
//pred指向尾结点
Node pred = tail;
//如果尾结点不为空,意味着队列已经被初始化
//但不一定有线程在队列中等待,因为队列的头结点对应的线程永远是null
if (pred != null) {
//让新建的结点的前驱结点指向尾结点
node.prev = pred;
//调用CAS尝试修改新建结点为尾结点
if (compareAndSetTail(pred, node)) {
//尝试成功则设置原来的尾结点的后继结点指向新建结点
//如果修改失败则意味着此时有其他线程加入队列且修改了尾结点
pred.next = node;
return node;
}
}
//如果队列还没有被初始化或者上面的CAS失败则调用enq
enq(node);
return node;
}
4. node加入到队列中
enq 一上来就是一个自旋 for(;😉 死循环,保证节点必能添加到队列中。如果tail== null,说明队列还没有初始化,就把head 设置为一个空节点(new Node()),然后tail指向head,此时tail就是空节点而不是null;不管怎样,第一次自旋没戏了,接着第二个自旋,如果tail != null,说明是有线程修改了。不管是第二次自旋还是一开始tail == null, 现在都要进入那个else 代码块: 还是和第三步一样将节点接到tail 后面,再CAS更改tail节点
private Node enq(final Node node) {
//死循环即自旋,保证了所有进入此方法的结点都能被正确的添加到队列中
for (;;) {
//t指向尾结点
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;
return t;
}
}
}
}
5.线程尝试获取锁失败且成功入队后
node节点入队列后就会开始自旋(死循环),一开始有个获取锁的标志位,failed = false, 和一个中断标志位 interrupted = false, 然后自旋。
- 如果node的前面就是head,说明是队列里头的第一个,就有机会能去拿锁,那就把node放在设置为head,然后原来的head GC 掉。
- 否则判断这个线程是否应该被挂起,shouldParkAfterFailedAcquire(p, node),parkAndCheckInterrupt(),如果判断成立,那就挂起,中断设置为true。
final boolean acquireQueued(final Node node, int arg) {
//获取锁失败的标识
boolean failed = true;
try {
//被中断的标识
boolean interrupted = false;
for (;;) {
//p指向新建结点的前驱结点
final Node p = node.predecessor();
//如果p是头结点即node是队列中第一个等待的线程
//则再次调用tryAcquire尝试获取锁
if (p == head && tryAcquire(arg)) {
//获取成功则设置当前结点为头结点(对应线程永远为null)
setHead(node);
//设置原来的头结点的后继节点为null,那么该结点不再
//被任何地方引用也不再引用任何其他对象,由GC负责回收
p.next = null; // help GC
//获取锁成功标识
failed = false;
//这里返回false则表示线程没有被申请中断
return interrupted;
}
//shouldParkAfterFailedAcquire:判断当前线程是否应该被挂起
//parkAndCheckInterrupt:挂起线程,清空线程的中断标识并返回
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
6. 判断线程是否应该挂起
先判断node的pre节点的状态,如果是signal,那么就可以挂起,如果是1(canceled),说明该pre节点应该终止了,不在参与锁竞争,那么就从pre起往前找,知道找到第一个不是canceled状态的节点。node.prev = pred = pred.prev;
如果既不是signal,也不是 canceled,那么尝试将pre节点的转态改为 signal compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取node的前驱结点pred的状态标识
//SIGNAL值为-1,标识该结点的后继结点需要被唤醒
//CANCELLED值为1,标识该结点被终止,不再参与锁竞争
int ws = pred.waitStatus;
//如果node的前驱结点pred的状态标识为SIGNAL
if (ws == Node.SIGNAL)
//可以被挂起
return true;
//如果node的前驱结点pred的状态标识为CANCELLED
if (ws > 0) {
//队列从后往前清除状态标识为CANCELLED的结点
do {
//这是第一个可能删除结点的地方
//从后往前直到第一个SIGNAL结点间的所有CANCELLED结点被删除
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
//设置前驱结点pred的后继结点为当前结点node
pred.next = node;
} else {
//若前驱结点pred的状态标识为初始状态则修改为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
//暂时不能被挂起
return false;
}
private final boolean parkAndCheckInterrupt() {
//系统调用挂起线程,线程在这里真正被阻塞
//从这里被唤醒有两种可能:一是unpark(),二是interrupt()
LockSupport.park(this);
//清除线程中断标识并返回
return Thread.interrupted();
}
7. 解锁操作
尝试释放锁,其实就是state -1,设置owner线程为 null ,释放成功就唤醒下一个节点
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//尝试释放当前锁
if (tryRelease(arg)) {
//h指向头结点
Node h = head;
//如果队列非空且头结点的状态标识为SIGNAL
if (h != null && h.waitStatus != 0)
//唤醒头结点的后继结点
unparkSuccessor(h);
//释放成功
return true;
}
//释放失败,意味着该锁被重入了,释放次数<加锁次数
return false;
}
protected final boolean tryRelease(int releases) {
//获取锁状态state并减去要释放的数量即1
int c = getState() - releases;
//如果释放锁的线程不是拥有锁的线程则抛异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果释放一次锁后锁状态变为空闲
if (c == 0) {
free = true;
//修改当前拥有锁的线程为null
setExclusiveOwnerThread(null);
}
//修改锁状态为0即空闲
setState(c);
return free;
}
唤醒下一任
先获取当前node的状态,如果是signal,就设置为0,然后交给下一任,需要从尾巴到头找到最先的不是canceled的node,作为下一任
private void unparkSuccessor(Node node) {
//获取当前结点的状态标识,此时可能为SIGNAL或者CANCELLED
//如果为CANCELLED意味着node已经被唤醒但发现自己被中断需要继续往后唤醒新的结点
int ws = node.waitStatus;
//如果当前结点状态标识为SIGNAL
if (ws < 0)
//调用CAS修改当前结点状态标识为0即初始状态
compareAndSetWaitStatus(node, ws, 0);
//s指向当前结点的后继结点
Node s = node.next;
//如果队列中没有等待的线程或者s的状态标识为CANCELLED
if (s == null || s.waitStatus > 0) {
s = null;
//队列从后往前找到最后一个(也就是从前往后第一个)
//状态标识不是CANCELLED的结点,且不能是当前结点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//如果队列中有等待被唤醒的线程
if (s != null)
//系统调用唤醒第一个等待的线程
//被阻塞的线程会在parkAndCheckInterrupt()中继续向下执行
LockSupport.unpark(s.thread);
}
8. 最后是一个cancelAcquire方法
自己的理解:当一个node获得锁后,它自身就会被置为NULL节点,然后GC,当一个node调用cancleAcquire后,首先这个Node的thread就会被置为null,然后找到它的前驱节点 pre ,如果 pre 是坏的(状态值大于0),那么就继续往前找,直到找到一个好的,然后把这个Node状态置为cancel
- 如果node是tail,那就把 pre 通过CAS改为tail
- 如果不是tail , 就把 pre.next = node.next,类似链表的操作
- 或者node已经是head,那就唤醒下一个节点
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
node.thread = null;
// Skip cancelled predecessors
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}