ReentrantLock通过lock方法调用公平锁的lock方法
final void lock() {
//调用AQS的acquire方法
acquire(1);
}
public final void acquire(int arg) {
//尝试获取锁
if (!tryAcquire(arg) &&
//如果获取锁失败,将线程通过addWaiter添加到等待队列中,然后在acquireQueued中进行自旋获取锁或park
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//如果status=0,说明锁是自由状态,可以尝试获得锁
if (c == 0) {
//判断等待队列是否没有等待线程或队列的头节点就是当前线程(即判断有没有必要去尝试获取锁),满足其中一种,就可以尝试获取锁
if (!hasQueuedPredecessors() &&
//如果没有等待线程或者第一个线程就是当前线程,进行CAS尝试获得锁,成功则设置status=1,锁持有线程exclusiveOwnerThread为当前线程
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果锁已被持有,判断是否是被当前线程持有,是则表示锁重入场景,将status+1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//不是当前线程持有锁,加锁失败
return false;
}
}
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;
/*当队列不为空时,head节点被设计为总是代表当前持有锁的线程,是个哨兵节点,当前持有锁线程是不参与排队的,head.thread就没有用,就置null,所以第一个等待线程节点是head.next,之所以第一次入队的时候才插入首节点,是因为队列为空时,线程来加锁不需要进队列,所以在第一次入队的时候才将首节点虚拟出来
1.tail=head分为两种情况,一种是tail=head=null,对应非竞争场景,队列为空,返回false,就不用排队直接去尝试获取锁;
或者队列中只有一个节点,这种场景又分为两种,一种是之前在排队的线程都出队了,最后一个线程也拿到锁了,这时它就是head并且也是tail,这种情况也可以去尝试获取锁,因为它其实就是第一个等待线程,
另一种场景是在第一次创建首节点的时候,只创建完首节点,还没插入第一个等待线程节点就进行线程切换了
2.如果队列中不止一个线程,并且第一个等待线程不是当前线程,返回true,这种情况不可以尝试获取锁,前面的都还没获取到锁,后面的就不用尝试获取锁了;如果第一个等待线程是当前线程(这个线程又来获取锁,重入场景),返回false,则不用排队(之所以不用排队是因为可能正好当前持有锁的线程释放了锁,而第一个等待线程即当前线程就有资格去获取锁),可以去尝试获取锁
3. 满足h != t && h.next == null的场景:
(1)一种场景是在enq方法里创建完首节点后,还没来得及给tail赋值,线程切换了,这种情况下,结果返回true,因为这种情况也表明在当前线程之前,已经有线程来获取锁了,那么当前线程就不能去获取锁。
(2)一种场景是在执行h != t这个判断的时候,队列中还是两个节点,但执行到(s = h.next) == null这个判断的时候,之前那个最后一个等待线程拿到了锁,结果就返回true,但是在上层方法里对结果进行了取反,也就是说在这种场景下,线程是不会马上去尝试获取锁的,按道理来讲,这种场景表明当前线程是第一个等待线程,是有资格去尝试获取锁的,我理解不去获取锁的原因是上个线程刚获得锁,不太可能立马释放掉,所以直接入队;
(3)一种场景是当前线程进入hasQueuedPredecessors的同时,另一个线程已经更改了tail,但还没有将head的next指向tail,这也发生在enq方法中
(4)一种场景是当前线程将head赋给h后,head被另一个线程移除队列,导致h的next为空,这种情况说明锁已经被占用*/
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
//tail不为null,说明等待队列不为空,将当前线程添加到等待队列尾部
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//等待队列为空时,先生成一个thread=null的头节点,然后再将当前线程的节点添加到等待队列,即等待队列第一次插入会生成两个节点,真正等待的第一个线程是head.next持有的线程
enq(node);
//返回当前线程节点
return node;
}
private Node enq(final Node node) {
//死循环
for (;;) {
Node t = tail;
//队列为空时,初始化一个哨兵节点,节点的thread=null,头尾都指向这个节点,这里没有return操作,会继续下一个for循环,走到else分支
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
}
//将当前线程节点插入到等待队列尾部,返回t,这里t实际就是head
else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
备注:设置哨兵节点是为了简化对边界条件的判断与维护,单独地生成一个结构对象与其他链表结点一致的结点。
例如设置了哨兵节点的等待队列中如果存在等待线程,那么head!=tail,head=tail的时候,表明队列中没有等待线程了(都为null,或者指向一个thread=null的节点,该节点是持有线程的节点,不是等待线程);但如果没有设置,则head=tail的时候,即可能是队列中没有等待线程(都为null),也可能有一个等待线程,增加了对边界条件的判断和维护。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//死循环
for (;;) {
final Node p = node.predecessor();
//如果前置节点是头节点,表明自己是第一个等待线程,之前尝试获取锁失败,自己需要入等待队列,入队后发现自己是第一个等待的线程,如果在这期间锁被释放,自己是有可能获得锁的,就能避免park操作,所以去尝试获取锁,这就是一次自旋操作
//如果前置节点不是头节点,那么它也不需要尝试获取锁了,因为前面还有其他线程在等待
if (p == head && tryAcquire(arg)) {
//说明前面线程释放了锁,当前线程获得了锁,则将当前线程设置为队头(head=node,head.thread=null, pre=null)
setHead(node);
//将释放完锁的线程节点从队列中移走
p.next = null; // help GC
failed = false;
return interrupted;
}
//如果前置节点不是头节点或尝试获取锁失败,则可能park
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//ws默认是0
//Node.SIGNAL=-1
//所以线程第一次进这个方法是执行else分支
int ws = pred.waitStatus;
//线程第二次进这个方法,就走这个分支,返回true,将线程park
//为什么不第一次直接返回true而要先改变ws,1.是为了第一个等待线程能多一次自旋,尽量不park 2.ws=0也是一个必要状态,这个状态是有意义的,其他地方会用到
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
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 {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
//将前置节点的ws置为-1,表示上个线程park了,之所以是下个线程将上个线程的状态置为-1,而不是自己置为-1,是因为只有确定了线程是park了才能置为-1,如果置为了-1但线程并没有park,那就不一致了,而如果线程已经park了,那它已经释放了CPU,就不可能自己再改变状态了
//第一个等待的线程在退出这个方法后将进行下一次for循环,即进行第二次自旋
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
//线程在这里阻塞
LockSupport.park(this);
/*1.如果是因为调用unpark或者interrupt将线程唤醒,就从这边开始往下继续执行,这里调用interrupted方法,判断线程是否被打断,并清除了中断信号,如果返回true表明是被用户中断,在acquireQueued方法里将interrupted设置为true,再次自旋获得锁,获得锁后返回acquire方法,并通过调用interrupt方法来还原用户的中断行为,但这种还原其实是没有什么意义的,因为外界并不能处理这个中断,但对于通过调用lockInterruptibly方法来加锁是有意义的,如果用户进行了中断,这边返回true,在acquireQueued方法里就会抛出异常,外界就可以通过捕捉这个异常来处理用户中断。
2.如果在执行LockSupport.park(this);之前线程就被打断了,执行park操作不会阻塞线程而会直接返回(park与wait的作用类似,但是对中断状态的处理并不相同。如果当前线程不是中断的状态,park与wait的效果是一样的;如果一个线程是中断的状态,这时执行wait方法会报java.lang.IllegalMonitorStateException,而执行park时并不会报异常,而是直接返回。)这里通过interrupted清除了中断标志,下一个循环再进行park时就能正常阻塞线程,而避免了在拿到锁之前一直在执行死循环。*/
return Thread.interrupted();
}
在可中断锁lockInterruptibly中,如果线程被中断,还会执行下面方法
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)) {
//将其前置节点的下一个节点设置为null
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
}
}