Semaphore.acquire()方法的底层原理

一、acquire() 的工作流程

当调用 acquire() 方法时,实际调用的是 AQS 的 acquireSharedInterruptibly(1) 方法。以下是其详细工作流程:

   // acquire() -> sync.acquireSharedInterruptibly(1),可中断
   public final void acquireSharedInterruptibly(int arg) {

        if (Thread.interrupted())
            throw new InterruptedException();

        // 尝试获取通行证,获取成功返回 >= 0的值
        if (tryAcquireShared(arg) < 0)
            // 获取许可证失败,进入阻塞
            doAcquireSharedInterruptibly(arg);
    }


1、Semaphore.acquire()

  public void acquire() throws InterruptedException {

        sync.acquireSharedInterruptibly(1); // 调用 AQS 的共享模式方法
  }


2、AQS.acquireSharedInterruptibly(int arg)

  public final void acquireSharedInterruptibly(int arg) throws InterruptedException {

        if (Thread.interrupted()) {
            throw new InterruptedException(); // 检查中断状态
        }

        if (tryAcquireShared(arg) < 0) { // 尝试获取许可
            doAcquireSharedInterruptibly(arg); // 许可不足,加入队列
        }
  }


3、Semaphore.tryAcquireShared(int acquires)

  protected int tryAcquireShared(int acquires) {
        for (;;) {
            int available = getState(); // 当前剩余许可数
            int remaining = available - acquires; // 计算剩余许可
            if (remaining < 0 || compareAndSetState(available, remaining)) {
                return remaining; // 返回负数表示需要排队,非负数表示成功,需要通过 CAS 更新 state 变量(将available更新remaining)
            }
        }
  }


4、doAcquireSharedInterruptibly(int arg),许可不足,加入队列

   private void doAcquireSharedInterruptibly(int arg) {
          // 将调用 Semaphore.aquire 方法的线程,包装成 node 加入到 AQS 的阻塞队列中
          final Node node = addWaiter(Node.SHARED);
          // 获取标记
          boolean failed = true;
          try {
              for (;;) {
                  final Node p = node.predecessor();
                  // 前驱节点是头节点可以再次获取许可
                  if (p == head) {
                      // 再次尝试获取许可,【返回剩余的许可证数量】
                      int r = tryAcquireShared(arg);
                      if (r >= 0) {
                          // 成功后本线程出队(AQS), 所在 Node设置为 head
                          // r 表示【可用资源数】, 为 0 则不会继续传播
                          setHeadAndPropagate(node, r); 
                          p.next = null; // help GC
                          failed = false;
                          return;
                      }
                  }
                  // 不成功, 设置上一个节点 waitStatus = Node.SIGNAL, 下轮进入 park 阻塞
                  if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                      throw new InterruptedException();
              }
          } finally {
              // 被打断后进入该逻辑
              if (failed)
                  cancelAcquire(node);
          }
   }


5、setHeadAndPropagate(Node node, int propagate)

  private void setHeadAndPropagate(Node node, int propagate) {  
  
        Node h = head;

        // 设置自己为 head 节点
        setHead(node);

        // propagate 表示有【共享资源】(例如共享读锁或信号量)
        // head waitStatus == Node.SIGNAL 或 Node.PROPAGATE,doReleaseShared 函数中设置的
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            // 如果是最后一个节点或者是等待共享读锁的节点,做一次唤醒
            if (s == null || s.isShared())
                doReleaseShared();
        }
  }


二、acquire() 的工作流程

当调用 acquire() 方法时,实际调用的是 AQS 的 acquireSharedInterruptibly(1) 方法。以下是其详细工作流程:


(1) 尝试获取许可

A、调用 tryAcquireShared(arg):

  • tryAcquireShared(arg) 是 AQS 的模板方法,由 Semaphore 实现

  • 在 Semaphore 中,tryAcquireShared(arg) 的逻辑是检查当前剩余许可数 state 是否足够

    • 如果 state >= 1,通过 int remaining = available - acquires; 操作将 state 减 1。

    • 如果 state < 1,返回负数,表示许可不足。

B、CAS 操作:

  • 使用 compareAndSetState(available, remaining) 方法原子性地更新 state 变量。

  • 如果 CAS 成功,表示线程成功获取许可;否则,重试或进入排队逻辑。


(2) 加入等待队列

A、创建节点并加入队列:

  • 如果 tryAcquireShared(arg) 返回负数(许可不足),线程会被封装为一个 Node 对象,加入 AQS 的 CLH 队列 尾部。

  • CLH 队列是一个双向链表,用于管理等待线程。

B、自旋与阻塞:

  • 线程在队列中自旋,尝试再次获取许可。

  • 如果仍然无法获取许可,线程通过 LockSupport.park() 方法被挂起,进入阻塞状态。

posted @ 2025-03-03 15:03  jock_javaEE  阅读(180)  评论(0)    收藏  举报