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.



相关文章
|
5月前
|
安全 关系型数据库 API
开源为何是中小企业数字化转型的首选?Websoft9为您解析第一性原理
在中小企业数字化转型中,Websoft9利用开源技术解决技术门槛高、预算有限和人才短缺的问题。文章从成本重构、技术自由与安全进化三方面剖析开源优势:零许可费用降低IT支出,模块化架构提供定制能力,透明性提升安全性。通过自动化部署与管理工具,Websoft9助力企业实现高效、灵活且安全的数字化转型,将开源技术作为最优解,推动“软件即能力”的新范式。
|
SQL 关系型数据库 数据库
手把手教你管理PostgreSQL数据库及其对象
手把手教你管理PostgreSQL数据库及其对象
568 0
|
2天前
|
Android开发 开发者 Windows
这是我设计的一种不关机,然后改造操作系统的软件设计思路2.0版本
本文介绍了在不重启系统的情况下实现操作系统改造的两种方案。第一种方案通过SLFM Recovery模式,在独立于操作系统的最高权限环境下完成系统更新与改造,并支持断电恢复与失败回滚。第二种方案采用多分区机制,通过SLFM套件在独立分区中完成系统改造,适用于可中断与不可中断服务场景,确保系统更新过程的安全与稳定。
211 132