AQS 原理和 ReentrantLock 源码(下)

简介: 本文中采用的 jdk 版本为 openjdk-1.8

ReentrantLock


一个简单的 Demo


import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockTest {
    static ReentrantLock lock = new ReentrantLock();
    static class T extends Thread {
        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread() + "开始尝试获取锁");
                if (lock.tryLock(10, TimeUnit.SECONDS)) {
                    System.out.println(Thread.currentThread() + "成功获取锁");
                    TimeUnit.SECONDS.sleep(5);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread() + "开始释放锁");
                lock.unlock();
            }
        }
    }
    public static void main(String[] args) {
        T t1 = new T();
        T t2 = new T();
        T t3 = new T();
        t1.start();
        t2.start();
        t3.start();
    }
}


加锁和解锁过程图解


image.png


加锁和解锁过程描述


  1. 在上面的程序中有三个线程同时去获取锁,同一个时刻只能又一个线程获取到锁,下面是进行入队去尝试获取锁的逻辑:


private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
  if (nanosTimeout <= 0L)
      return false;
  final long deadline = System.nanoTime() + nanosTimeout;
  final Node node = addWaiter(Node.EXCLUSIVE); // 入队
  boolean failed = true;
  try {
      for (;;) {
          final Node p = node.predecessor();
          if (p == head && tryAcquire(arg)) { // 如果是头节点并且获取锁成功
              setHead(node);
              p.next = null; // help GC
              failed = false;
              return true;
          }
          // 获取锁超过最大等待时间
          nanosTimeout = deadline - System.nanoTime();
          if (nanosTimeout <= 0L)
              return false;
          // 获取锁失败进入阻塞 并且 并且超过自旋等待时间
          if (shouldParkAfterFailedAcquire(p, node) &&
              nanosTimeout > spinForTimeoutThreshold)
              // 进入阻塞 nanosTimeout 为阻塞时间
              LockSupport.parkNanos(this, nanosTimeout);
          if (Thread.interrupted())
              throw new InterruptedException();
      }
  } finally {
      if (failed)
          cancelAcquire(node);
  }
}


  1. 非公平锁尝试加锁的逻辑, 如果没有线程持有锁,那么就去通过 CAS 尝试加锁,如果是当前线程持有锁那么就 state + 1 累计,这里也可以看出 ReentrantLock 支持重入。


// 非公平锁的逻辑
// 如何理解插队, 这里的插队是当前队列中被唤醒的线程, 和当前加入的线程都可以被执行
// 如果当前加入线程比队列中唤醒的线程先获取到锁, 就是插队现象
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 无锁状态, 尝试竞争
    if (c == 0) {
        if (compareAndSetState(0, acquires)) { //是否获取到锁
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 当前线程持有锁, state 计数 +1
    else if (current == getExclusiveOwnerThread()) { //判断是否是重入
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}


  1. 如果获取到锁, 会将当前节点设置为头节点。并且返回 true 如果获取锁失败,并且超过获取锁的自旋时间那么当前线程将进入阻塞,阻塞是通过调用 LockSupport.parkNanos(this, nanosTimeout);实现的。在这个过程中可能调用多次 shouldParkAfterFailedAcquire 方法。


  shouldParkAfterFailedAcquire 可以用来修改当前节点的状态,和对链表上无  效的节点出队


/** 当获取锁失败后, 检查更新新节点状态如果是需要阻塞返回, true
 * <p>
 * 一个前继节点 waitStatus = 0, 第一次将继续设置为 SIGNAL, 告诉当前线程准备进入阻塞, 此时依旧获取不到, 当前线程进入阻塞
 *
 * @param pred 前继节点
 * @param node 当前节点
 * @return {@code true} if thread should block
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus; // 前继节点的状态, 第一次进入的话, 一定是 0
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
        do {
            // 出队, 剔除无效的节点
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 第一次进来, pred.waitStatus = 0 执行这个分支
        // 将前继节点的状态修改为 SIGNAL, 表示 pred.next 节点需要被唤醒(此时准备进入阻塞, 但是还未被阻塞, 再次获取锁失败之后才会被阻塞)
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}


  1. 解锁逻辑,下面是解锁的逻辑, 首先会进行解锁,如果 state 的状态修改为 0, 然后再去唤醒队列中排队的线程。


// 解锁
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        // 判断是否有需要唤醒的线程
        if (h != null && h.waitStatus != 0) //waitStatus 的值为 0, 只有当后继存在节点才会被设置为该值不为 0, 此时需要唤醒后继线程
            unparkSuccessor(h);
        return true;
    }
    return false;
}
// tryRelease 
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    // 判断是否是当前线程持有锁
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
      // 如果 state == 0 表示当前线程不在占有该锁
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}
// 唤醒队列中的线程
private void unparkSuccessor(Node node) {
  // 将当前节点状态修改为 0  
    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);
}


  1. 当前节点被唤醒逻辑,首先会在 shouldParkAfterFailedAcquire 方法中出队,然后尝试加锁如果加锁成功就返回 true.



相关文章
【ReentrantReadWriteLock的实现原理】
【ReentrantReadWriteLock的实现原理】
|
7月前
|
Java
ReentrantLock(可重入锁)源码解读与使用
ReentrantLock(可重入锁)源码解读与使用
|
7月前
|
Java
从源码入手详解ReentrantLock,一个比synchronized更强大的可重入锁
【5月更文挑战第6天】从源码入手详解ReentrantLock,一个比synchronized更强大的可重入锁
40 1
|
7月前
|
安全 Java 程序员
Java多线程基础-17:简单介绍一下JUC中的 ReentrantLock
ReentrantLock是Java并发包中的可重入互斥锁,与`synchronized`类似但更灵活。
62 0
|
7月前
|
安全 Java
ReentrantLock 原理你都知道吗?
通过以上步骤和示例代码,你应该对 ReentrantLock 的工作原理有了清晰的理解。欢迎关注威哥爱编程,一起学习成长。
|
Java 程序员 API
AQS 原理解读
AQS 原理解读
|
安全
AQS学习:ReentrantLock源码解析
AQS学习:ReentrantLock源码解析
52 0
|
Java API 调度
synchronized 和 ReentrantLock 的实现原理是什么?它们有什么区别
synchronized 和 ReentrantLock 的实现原理是什么?它们有什么区别
87 0
AQS(abstractQueuedSynchronizer)锁实现原理详解
AQS(abstractQueuedSynchronizer)抽象队列同步器。其本身是一个抽象类,提供lock锁的实现。聚合大量的锁机制实现的共用方法。
155 0