Java ReentrantLock
加锁逻辑将分成三个部分来看:

  • 竞争锁
  • 加入等待队列
  • 阻塞等待

    竞争锁

    先从公平锁入手

    1. public void lock() {
    2. // sync的实例是new FairSync()
    3. sync.acquire(1);
    4. }
    5. // 加锁的代码就是这几行
    6. public final void acquire(int arg) {
    7. if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    8. selfInterrupt();
    9. }

    上述代码可以拆分成以下几段:

    1. // 竞争锁
    2. tryAcquire(arg)
    3. // 加入等待队列
    4. addWaiter(Node.EXCLUSIVE)
    5. // 阻塞等待
    6. acquireQueued(node, arg)
  • 竞争锁

    1. protected final boolean tryAcquire(int acquires) {
    2. // 获取当前线程
    3. final Thread current = Thread.currentThread();
    4. // 获取当前state状态
    5. int c = getState();
    6. // 如果当前state是没有任何线程抢占的话
    7. if (c == 0) {
    8. // 如果等待队列中有任何一个等待的节点,都不会抢占锁
    9. if (!hasQueuedPredecessors() &&
    10. // CAS抢占锁成功
    11. compareAndSetState(0, acquires)) {
    12. // 抢占成功后,标记当前线程已经抢占到锁了。
    13. setExclusiveOwnerThread(current);
    14. // 返回加锁成功
    15. return true;
    16. }
    17. }
    18. // 如果是同一个线程重复加锁的情况下
    19. else if (current == getExclusiveOwnerThread()) {
    20. // 在这种情况下,只是简单地操作state
    21. int nextc = c + acquires;
    22. if (nextc < 0)
    23. throw new Error("Maximum lock count exceeded");
    24. // 因为当前线程已经加锁成功了,再次加锁的话,直接在state上增加加锁次数即可。
    25. setState(nextc);
    26. // 返回加锁成功
    27. return true;
    28. }
    29. // 如果已经有别的线程加锁了,或者还有很多线程在排队等待,那么返回false加锁失败。
    30. return false;
    31. }

    上述代码分几部分:

  1. 如果当前state=0,也就是没有任何线程抢占锁的情况下
    1.1: 没有等待队列的情况下,可以CAS抢占锁
    1.2: 有等待队列的话,该队列中第一个等待节点不是当前线程,不可以抢占锁,因为这是公平锁。

ReentrantLock加锁源码浅析 - 图1
如果当前等待队列中还有任意节点,并且当前节点中的线程不是当前线程,说明有其他线程处于等待过程中,那么当前线程就应该乖乖排队去。
1.3: 有等待队列,并且当前第一个等待节点就是当前线程,可以抢占锁。这种情况会出现在线程刚从阻塞中被唤醒的时候。
ReentrantLock加锁源码浅析 - 图2
假如当前线程是被刚刚唤醒的,并且它处于等待队列中的第一个等待的位置,那么这个时候是可以去抢占锁的。

  1. 如果已经抢占了锁的线程就是当前线程。这种情况叫做重入。
    示例如下:

    1. ReentrantLock lock = new ReentrantLock();
    2. try {
    3. // 加锁
    4. lock.lock();
    5. // 执行业务逻辑
    6. System.out.println("获取的锁");
    7. try {
    8. // 再次获取锁
    9. lock.lock();
    10. // 执行业务逻辑
    11. System.out.println("再次获取的锁");
    12. } finally {
    13. // 解锁
    14. lock.unlock();
    15. }
    16. } finally {
    17. // 解锁
    18. lock.unlock();
    19. }

    小结一下:

  2. 如果当前锁未被抢占,并且没有其他线程等待,那么直接抢占锁

  3. 如果当前锁未被抢占,有其他线程等待,不可用抢占锁
  4. 如果当前锁被当前线程抢占了,那么直接重入即可
  5. 不符合上述情况,直接加锁失败。也就是锁被其他线程抢占了,或者目前还有其他线程处于等待中,都会导致公平锁加锁失败。
    1. // 判断等待队列中是否有其他线程等待
    2. public final boolean hasQueuedPredecessors() {
    3. Node h, s;
    4. // 如果等待队列头节点不为空,说明等待队列已经创建出来了。否则直接返回false。
    5. if ((h = head) != null) {
    6. // 如果头节点后面的节点为空,或者该节点的状态是取消状态
    7. if ((s = h.next) == null || s.waitStatus > 0) {
    8. s = null; // traverse in case of concurrent cancellation
    9. // 从后往前遍历,直至最后一个状态小于等于0的节点。只有小于等于0的节点才是正常的可以竞争锁的节点。
    10. for (Node p = tail; p != h && p != null; p = p.prev) {
    11. // 发现小于等于0的节点,就赋值给s
    12. if (p.waitStatus <= 0)
    13. s = p;
    14. }
    15. }
    16. // 如果最终得到的节点不为空。有可能当前没有任何等待的节点,s=null。
    17. // 并且这个不为空的等待线程不是当前线程。其实就是说明前面还有其他线程排队。
    18. if (s != null && s.thread != Thread.currentThread())
    19. // 返回true,说明有其他线程在排队。
    20. return true;
    21. }
    22. // 1.如果等待队列不存在,直接返回false
    23. // 2.如果当前等待队列中,没有任何其他节点的waitStatus<=0
    24. return false;
    25. }
    至此,线程竞争锁的逻辑就完毕了。

    加入等待队列

    1. private Node addWaiter(Node mode) {
    2. // 创建一个节点,该节点默认
    3. // waitStatus=0, thread=currentThread
    4. Node node = new Node(mode);
    5. // 开启自旋
    6. for (;;) {
    7. // 取出尾节点
    8. Node oldTail = tail;
    9. // 如果尾节点不为空
    10. if (oldTail != null) {
    11. // 设置node的前一个节点为尾节点
    12. node.setPrevRelaxed(oldTail);
    13. // CAS把尾节点设置为node
    14. if (compareAndSetTail(oldTail, node)){
    15. // 如果CAS设置成功,那么就把oldTail的next引用设置成node
    16. oldTail.next = node;
    17. // 返回node节点
    18. return node;
    19. }
    20. } else {
    21. // 如果尾节点为null,说明等待队列还不存在,这个时候就要准备初始化等待队列。
    22. // 初始化完毕后继续自旋,最终把新创建的节点添加进等待队列
    23. initializeSyncQueue();
    24. }
    25. }
    26. }
    27. // 初始化等待队列。其实是一个双向链表,所以只要初始化head、tail节点即可。
    28. private final void initializeSyncQueue() {
    29. Node h;
    30. // CAS设置head节点。如果head节点为null,就设置为new Node()。该node节点waitStatus=0,thread=null。
    31. if (HEAD.compareAndSet(this, null, (h = new Node())))
    32. // 头节点设置成功后,尾节点初始化为同一个节点。
    33. tail = h;
    34. }

    1.初始化等待队列

    1. // 初始化等待队列。其实是一个双向链表,所以只要初始化head、tail节点即可。
    2. private final void initializeSyncQueue() {
    3. Node h;
    4. // CAS设置head节点。如果head节点为null,就设置为new Node()。该node节点waitStatus=0,thread=null。
    5. if (HEAD.compareAndSet(this, null, (h = new Node())))
    6. // 头节点设置成功后,尾节点初始化为同一个节点。
    7. tail = h;
    8. }
    ReentrantLock加锁源码浅析 - 图3

    2.添加新的节点

    1. // 创建新节点
    2. Node node = new Node(mode);
    3. // 取出尾节点
    4. Node oldTail = tail;
    ReentrantLock加锁源码浅析 - 图4
    1. // 设置node的前一个节点为尾节点
    2. node.setPrevRelaxed(oldTail);
    3. // CAS把尾节点设置为node
    4. if (compareAndSetTail(oldTail, node)){
    5. // 如果CAS设置成功,那么就把oldTail的next引用设置成node
    6. oldTail.next = node;
    ReentrantLock加锁源码浅析 - 图5
    经过上面几步,新的节点就被添加到等待队列中了。
    有一个注意点需要提的是:
    为什么判断等待队列是否存在,使用的是if(tail!=null),而不是if(head!=null)?
    这个问题其实跟初始化等待队列有关系,初始化的时候是使用CAS设置head节点,成功后再设置tail节点。也就是说,队列初始化完毕的标识是tail!=null。
    如果使用if(head!=null)来判断队列已经存在,那么有可能此时tail还没有初始化完毕。就会导致使用tail节点的时候空指针异常。

    阻塞等待

    1. final boolean acquireQueued(final Node node, int arg) {
    2. // 默认线程未被打断
    3. boolean interrupted = false;
    4. try {
    5. // 开启自旋
    6. for (;;) {
    7. // 获取当前节点的前一个节点
    8. final Node p = node.predecessor();
    9. // 如果前一个节点是head节点,那么就尝试竞争锁
    10. if (p == head && tryAcquire(arg)) {
    11. // 竞争锁成功,把当前节点设置为head节点
    12. setHead(node);
    13. // 把前一个节点和当前节点断开
    14. // 因为当前节点已经设置为head节点了,之前的head就可以GC了
    15. p.next = null; // help GC
    16. // 返回是否当前线程被打断。
    17. // 这个返回结果的作用会被用在lockInterruptibly()这个方法上。
    18. // lock()方法可忽略。
    19. return interrupted;
    20. }
    21. // 判断当前节点是否应该阻塞。
    22. if (shouldParkAfterFailedAcquire(p, node))
    23. // 下面这个代码可以翻译成:
    24. // if(parkAndCheckInterrupt()){
    25. // interrupted = true;
    26. // }
    27. interrupted |= parkAndCheckInterrupt();
    28. }
    29. } catch (Throwable t) {
    30. // 抛出任何异常,都直接取消当前节点正在竞争锁的操作
    31. // 如果在等待队列中,就从等待队列中移除。
    32. // 如果当前线程已经抢占到锁了,那么就解锁。
    33. cancelAcquire(node);
    34. // 如果当前线程已经被中断
    35. if (interrupted)
    36. // 重新设置中断信号
    37. selfInterrupt();
    38. // 抛出当前异常
    39. throw t;
    40. }
    41. }

    1.获取当前节点的上一个节点

    ```java // 获取当前节点的前一个节点 final Node p = node.predecessor();

final Node predecessor() { // 上一个节点 Node p = prev; // 如果为null,直接抛异常 if (p == null) throw new NullPointerException(); else // 返回上一个节点 return p; }

  1. <a name="sJJs9"></a>
  2. ### 2.如果上一个节点为head节点
  3. ```java
  4. // 获取当前节点的前一个节点
  5. final Node p = node.predecessor();
  6. // 如果前一个节点是head节点,那么就尝试竞争锁
  7. if (p == head && tryAcquire(arg))

ReentrantLock加锁源码浅析 - 图6

3.抢占成功锁后

  1. // 竞争锁成功,把当前节点设置为head节点
  2. setHead(node);
  3. // 把前一个节点和当前节点断开
  4. p.next = null;

ReentrantLock加锁源码浅析 - 图7

4.判断当前节点的状态

  1. private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
  2. // 获取前一个节点的状态
  3. int ws = pred.waitStatus;
  4. // 如果状态等于-1。Node.SIGNAL的值就是-1
  5. if (ws == Node.SIGNAL)
  6. // 直接返回true,这个时候就要准备阻塞。
  7. return true;
  8. // 如果状态值大于0,说明是要取消的节点。
  9. if (ws > 0) {
  10. // 跳过“取消”状态节点
  11. do {
  12. node.prev = pred = pred.prev;
  13. } while (pred.waitStatus > 0);
  14. pred.next = node;
  15. } else {
  16. // ws小于等于0的话,直接把前一个节点的状态置为-1
  17. // 因为新创建的节点初始化状态是0,
  18. // 那么意味着执行到这里后,还要返回去重新自旋一次才能返回true。
  19. pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
  20. }
  21. // 返回false
  22. return false;
  23. }

5.当前线程阻塞

  1. private final boolean parkAndCheckInterrupt() {
  2. // 阻塞当前线程。
  3. // 1\. 调用LockSupport.unpark()才能重新唤醒被阻塞的线程。
  4. // 2.调用thread.interrupt()也可以唤醒阻塞线程。
  5. LockSupport.park(this);
  6. // 判断当前线程是否被打断。
  7. // 如果当前线程是被打断的,那么返回true,否则返回false。
  8. return Thread.interrupted();
  9. }

小结一下:

  1. 先获取当前节点的前一个节点,如果是head节点,那么尝试竞争锁
    1. 竞争锁成功后,重置head节点,返回false(代表没有被打断)。
  2. 如果前一个节点状态小于等于0,那么置为-1。
    1. 重新自旋一次,从第一步开始
    2. 如果前一个节点状态等于-1,返回true,准备阻塞。
  3. 调用LockSupport.park()阻塞当前线程,直至unpark()或者interrupt()唤醒当前线程。
    1. 通过unpark()唤醒,没有被打断,返回false
    2. 通过interrupt()唤醒,被打断,返回true。
  4. 被唤醒的线程又开始自旋,直至获取到锁后返回是否被打断的结果。
    1. 如果是被打断后获取锁返回,那么返回true。
    2. 否则返回false。 ```java public final void acquire(int arg) { // 尝试获取锁 if (!tryAcquire(arg) && // addWaiter(Node.EXCLUSIVE):竞争锁失败后,添加到等待队列 // acquireQueued(node, arg):阻塞等待,自旋获取锁后,返回判断是否被打断 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 如果被打断,需要恢复中断信号 selfInterrupt(); }

// 其实就是重新中断一次。 // 因为执行过Thread.interrupted()方法后,会让中断信号重置为false。 static void selfInterrupt() { Thread.currentThread().interrupt(); } ```