AQS之ReentrantLock

ReentrantLock中通过创建内部类Sync继承AbstractQueuedSynchronizer来管理同步状态,Sync又有公平和非公平锁两种模式:

公平锁

在公平锁模式下调用ReentrantLock.lock(),内部调用FairSync.lock(),其中调用AQS的acquire(1)获取一个独占锁。AQS并未实现tryAcquire()方法,由FaciSync实现。在tryAcquire()中:

  1. 首先检查state状态,若有锁定并且锁定线程不是当前线程则返回false。
  2. 若无锁定并且同步队列中无等待线程,则使用CAS将state改为锁定状态,若成功则将当前线程设为独占锁拥有者。
  3. 若当前线程为独占锁拥有者(重入),则将state加一,不需要使用CAS,因为已经拥有独占锁。
      final void lock() {
          acquire(1);
      }
	 // AQS
     public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

			selfInterrupt();
    }

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

若tryAcquire()无法获取到锁,则调用AQS.addWaiter(Node mode),先将当前线程包装成一个独占类型的同步队列结点,如果Tail结点不为空,则将当前Node的prev指针指向Tail,Tail的next指针指向当前node。然后调用AQS.enq()将当前Node加入同步队列中,若Tail结点为空,创建一个新的空Node,赋值给Head/Tail。再将当前Node前置prev指向Tail,Tail的后继next指向当前Node。

    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

将当前线程包装成结点放入同步队列之后,调用AQS.acquireQueued()方法,当前线程会一直在所属结点的前驱上轮询:

  1. 当前驱结点为Head时,证明当前结点在所有等待结点中排在第一个。调用FairSync.tryAcquire()尝试加锁,如果成功则将当前结点与Head结点的互相引用解绑,将当前结点设为Head,Thread属性清空,当前线程开始执行临界区代码。
  2. 前驱不为Head时,检查当前线程是否应该阻塞,若要阻塞,阻塞时需要检查线程是否被打断。检查线程是否应该被阻塞时,需要用到前驱结点状态waitStatus:
    若waitStatus = Node.SIGNAL,表示应该阻塞当前线程。
    若waitStatus = Node.CANCEL,表示前驱结点已经被取消,应该将前驱指针指向前驱的前驱。若前驱状态仍为CANCEL循环之前的替换。直至前驱waitStatus!=CANCEL,将前驱Next指向当前结点,不阻塞当前线程。
    若waitStatus= INTITAL or PROPAGATE,使用CAS操作更新前驱结点waitStatus至SIGNAL,不阻塞当前线程。

如果线程在被等待过程中被打断,返回true。

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

非公平锁

非公平锁的加锁过程相较于公平锁,只少了一个步骤。在尝试加锁时,公平实现会检查同步队列中是否有前驱结点,若有则不尝试加锁,将当前线程包装结点放入队列;而非公平实现不会检查同步队列中是否有元素,直接尝试加锁,若加锁不成功再放入同步队列之中。

      final boolean nonfairTryAcquire(int acquires) {
          final Thread current = Thread.currentThread();
          int c = getState();
          if (c == 0) {
              if (compareAndSetState(0, acquires)) {
                  setExclusiveOwnerThread(current);
                  return true;
              }
          }
          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;
      }

unlock

Reentrant.unlock()调用AQS.release():

  1. 首先使用Sync.tryRelease(),若state-release = 0,则将独占锁拥有线程设为Null,再将state更新为0,返回true,否则返回false。
  2. 若第一步锁已经释放,Head结点不为Null并且waitStatus != 0则唤醒后续结点线程。若=0,则可以判断队列后续无结点或者结点并没有被打断,无需唤醒。

    public final boolean release(int arg) {
	if (tryRelease(arg)) {
	  Node h = head;
	// h.waitStatus != 0 等价于 h.waitStatus == -1(SIGNAL),表示后继存在等待结点
	if (h != null && h.waitStatus != 0)
		unparkSuccessor(h);
		return true;
	 }
	 return false;
    }

    protected final boolean tryRelease(int releases) {
        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;
    }
		

可中断加锁

tryLock(long timeout, TimeUnit unit)Lock的区别,在尝试加锁失败后,会将调用LockSupport.parkNanos(this, nanosTimeout)将自己限时阻塞,若阻塞过程中被打断,则响应中断抛出异常。

    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)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
posted @   cd_along  阅读(36)  评论(0编辑  收藏  举报
编辑推荐:
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示