ReentrantLock源码解析

72 阅读6分钟

ReentrantLock

AQS

AbstractQueuedSynchronizer 抽象队列同步器 内部维护两个队列/链表:

  1. 同步队列(双向链表)
  2. 条件队列 (单向链表)

条件队列中的线程必须进入同步队列才能得到资源执行,类似synchronized的wait后进入waitset,需要notify进入cxq/EntryList才能抢到资源

默认初始化 为非公平锁

public ReentrantLock() {
    //非公平锁
    sync = new NonfairSync();
}

Node的几个状态

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代表后面有节点,-2代表自身调用了await()方法后进入条件队列

非公平锁

加锁逻辑

//NonfairSync重写方法
final void lock() {
    if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
    else
        //获取锁失败
            acquire(1);
}

protected final boolean tryAcquire(int acquires) {
	return nonfairTryAcquire(acquires);
}


//acquire方法
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
非公平锁下尝试获取锁方法
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
                //exclusiveOwnerThread  标记当前线程持有锁
                setExclusiveOwnerThread(current);
                return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        //锁重入
        int nextc = c + acquires;
        //标记次数 state > 1代表持有锁  state-1代表重入次数
        setState(nextc);
        return true;
    }
    return false;
}
添加Node节点至队列,保证入队列尾部
private Node addWaiter(Node mode) {
    //包装线程为Node节点
    Node node = new Node(Thread.currentThread(), mode);
   //尾部节点不为空 尝试cas添加至尾端
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //cas添加失败 或者尾部节点为空 ,死循环尝试 直至成功
    enq(node);
    return node;
}

//返回原队尾节点
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) {  
                //尾部节点为空 初始化一个空节点 作为head 
                if (compareAndSetHead(new Node()))
                        tail = head;
        } else {
               //存至尾端
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                        t.next = node;
                        return t;
                }
        }
    }
}
死循环判断是否需要再次尝试/阻塞
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //获取前一个节点
            final Node p = node.predecessor();
            //如果前一个节点为头节点 尝试cas获取锁一次
            if (p == head && tryAcquire(arg)) {
               //获取成功 将当前节点设置为头节点
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //没有获取到锁:1.前面不只1个节点 2.前面只有一个节点 但是需要抢的锁资源未释放
            if (shouldParkAfterFailedAcquire(p, node) &&
                //调用 LockSupport.park  
                //返回是否被中断,中断后不会抛出异常,直接返回是否被中断
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
阻塞前,设置SIGNAL状态

设置prev前一个节点的节点为SIGNAL状态,旨在告知prev节点执行完毕后需要唤醒后续节点, 那么此时当前节点就可以准备进入阻塞状态了

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //SIGNAL -1,CONDITION -2,PROPAGATE -3, 1 CANCELLED
    int ws = pred.waitStatus;
    //如果前面的节点已经是 SIGNAL 就返回true ,代表进入阻塞,等待前面节点唤醒
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
        //已取消
        do {
            //当前节点的prev指向前面未取消的节点
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        //双端的 前面的也要指过来
        pred.next = node;
    } else {
        //设置前面节点为SIGNAL 代表需要等待前面节点通知,此时返回false不进入阻塞状态,再度尝试一次才会阻塞,且再度的尝试会确保cas一定成功
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
线程被中断后 移除队列
private void cancelAcquire(Node node) {
    if (node == null)
        return;

    node.thread = null;

    Node pred = node.prev;
    //找到前一个未取消的节点
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;
    Node predNext = pred.next;

    node.waitStatus = Node.CANCELLED;

    //当前为尾部节点 重新设置尾部节点  
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null);
    } else {
        //连接前后节点
        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 {
            //找到node的下一个节点 或者 从尾部往前找到第一个未取消的节点,调用 LockSupport.unpark唤醒
            unparkSuccessor(node);
        }
        node.next = node; // help GC
    }
}
唤醒后续节点
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 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;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

释放锁逻辑

public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
        //存在重入情况下 一次释放不完全,所以有if判断
	if (tryRelease(arg)) {
		Node h = head;
                //head节点不为空且状态非0 表示后续有节点需要唤醒
		if (h != null && h.waitStatus != 0)
			unparkSuccessor(h);
		return true;
	}
	return false;
}


protected final boolean tryRelease(int releases) {
       //次数-1 ,未重入时 -1就等于0了
	int c = getState() - releases;
	if (Thread.currentThread() != getExclusiveOwnerThread())
		throw new IllegalMonitorStateException();
	boolean free = false;
	if (c == 0) {
		free = true;
		setExclusiveOwnerThread(null);
	}
	setState(c);
	return free;
}

公平锁

static final class FairSync extends Sync {
   private static final long serialVersionUID = -3000897897090466540L;

   final void lock() {
       acquire(1);
   }

   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;
   }
}

可以看到公平锁和非公平锁的区别就是 严格按照FIFO先来后到的顺序,不去尝试直接抢锁

lock.newCondition()

final ConditionObject newCondition() {
    //就创建了一个ConditionObject对象
    return new ConditionObject();
}

 //每个ConditionObject内部都维护了一个 头 尾指针, 这表明ConditionObject内部也自成一个队列
 public class ConditionObject implements Condition, java.io.Serializable {
        
        private transient Node firstWaiter;
        
        private transient Node lastWaiter;
}

condition.signal()

一般操作是await 然后才会signal, 我们先看下signal方法,因为有可能node的状态莫名被其他线程改变了,然后一头雾水

public final void signal() {
     //校验当前线程是不是持锁
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //上面说到 ConditionObject内部有两个节点,组成一个队列, 这里就看取出头部进行操作
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

doSignal

private void doSignal(Node first) {
    do {
         //将队列头部的下一个节点变为头部
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

 final boolean transferForSignal(Node node) {
        //将CONDITION状态修改为0
	if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
		return false;
        //前文有介绍 加入到同步队列的尾部, 同时返回原队列尾部
	Node p = enq(node);
	int ws = p.waitStatus;
        //如果原队尾节点取消了,就将其状态改为signal,唤醒当前节点的线程
	if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
		LockSupport.unpark(node.thread);
	return true;
}

也就是说signal方法就是从ConditionObject的条件队列里的头部节点挪至同步队列。 是不是和 object.notify很像(notify是移动是cxq/EntryList的头部)

那么相应的signalAll()就是把条件队列的所有节点挨个放入同步队列的尾部。

condion.await()

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //将线程包装为状态为CONDITION的Node 存放到ConditionObject的尾部
    Node node = addConditionWaiter();
    //一次将锁标志位清0,并唤醒同步队列的下一个线程
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //如果自己debug,多线程抢锁/signal,下面这个while可能进不去,有可能当前线程刚把自己放到条件队列的尾部,下一个线程就给他挪到同步队列了
    while (!isOnSyncQueue(node)) {
       //此时在条件队列 没有其他线程给他挪到同步队列 就进入阻塞了
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    //此时线程未阻塞 死循环尝试竞争锁
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

判断是否已进入同步队列

final boolean isOnSyncQueue(Node node) {
     //锁竞争不那么激烈  一般是直接返回false了
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    if (node.next != null) // If has successor, it must be on queue
        return true;
    //有可能自己是同步队列尾部节点,从尾部向前查找,找到就证明已经在同步队列里
    return findNodeFromTail(node);
}

那么await()方法就是将当前线程包装为CONDITION的Node 存放到ConditionObject的尾部并进入阻塞,等待其他线程调用signal/sinalAll方法将Node节点移至同步队列,如果进入阻塞前已被挪至同步队列,会尝试获取锁,获取不成功再进入阻塞

object.notify/notifyAll是提取到cxq/EntryList的队首,所以会优先唤醒,而这里仍是队尾

总结

ReentrantLock底层是AQS抽象队列同步器,可根据需要创建公平锁和非公平锁。
公平锁和非公平锁的区别就是非公平锁会在抢锁之初直接尝试cas操作,哪怕有其他线程正在阻塞等待。
AQS内部维护一个双向链表作为同步队列,未抢到锁的线程通通进入队列阻塞等待
除此之外,可以使用lock.newCondition()获取一个ConditionObject对象,ConditionObject内部维护一个单向的链表条件队列,使用condition.await()会将自身持有的锁资源释放,唤醒同步队列的下一个节点,同时还会将自身放入条件队列,后进入阻塞状态。调用condition.notify/nofifyAll会将条件队列的头部节点放入到同步队列进行排序获取锁资源,区别是挪动一个还是多个节点。
await()/signal()同 Object.wait() 和 Object.notify()都需要自身持有锁资源才能调用,防止引起不必要的锁竞争和造成错误。