AQS源码学习

JavaDoc定义

AQS提供一个框架来实现基于FIFO等待队列的阻塞锁和相关的synchronizers(semaphores信号量,events事件等等)。

它是为了大多数synchronizers而设计的基础类,这些synchronizers都依赖于一个原子的int值来代表状态state。它的子类必须定义一个protected方法来修改state值,这个方法定义了这个state值对于这个对象被获取和被释放意味指什么。考虑到这些,该类的其他方法执行所有的队列和阻塞机制。

子类可以维护其他的state字段,但是只有使用getState,setState和compareAndSetState方法自动更新的int值才会被同步跟踪。

子类需要被定义成非public的内部帮助类,用于实现它们包装类的同步属性。类AbstractQueuedSynchronizer没有实现任何的同步接口。相反,它定义了类似acquireInterruptibly这样的方法,这些方法可以被具体的锁和相关的synchronizers来实现它们的公共方法。

这个类独占模式Exclusive和共享模式Shared的任一种或者全部两种。当使用独占模式获取锁时,其他线程无法尝试获取到锁。共享模式可以被多个线程获取成功(但不是必须的)。

在不同模式中等待的线程共享相同的FIFO队列。通常,实现的子类只支持一种模式(独占、共享),但是在ReadWriteLock(读写锁)中它们同时支持了两种模式。只支持独占或共享模式的子类不需要定义支持未使用模式的方法。

AQS源码解读

常用的字段

head: 等待队列头部
tail:等待队列尾部

acquire 获取独占锁

public final void acquire(int arg) {
      if (!tryAcquire(arg) &&
           acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
           selfInterrupt();
   }

先尝试获取独占锁,tryAcquire方法交由具体的锁实现。如果获取成功,就不管了。
如果获取失败,那么就要进行加入等待队列的操作。怎么加入等待队列的呢,先看一下addWaiter方法

addWaiter
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;
    }

新建一个Node,Node里保存了线程为当前线程,模式为Node.EXCLUSIVE独占模式。pred指向等待队列尾部。
如果pred不为空,那么就把当前线程的前一个线程指向pred,在cas pred为当先线程。如果cas成功,pred的next就指向当前线程,返回当前线程。见下图

在这里插入图片描述

enq

如果pred即tail为空呢?直接进行了enq操作

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

tail为空,意味着等待队列为空。但是这个等待队列并不是类似LinkedBlockingQueue之类的数据结构。所以,AQS建立了一个辅助节点Node,存了个空线程,作为队列的头部。其他的线程都往尾部加。

那么AddWaiter看好了,再看看acquireQueued做了什么。

acquireQueued
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor(); // 获取当前线程的prev线程
                if (p == head && tryAcquire(arg)) { // 如果当前线程的prev是head,那么就代表head是那个辅助Node,当前线程就是实际上的队列头部,执行尝试获取锁。
                    setHead(node);// 如果获取成功,执行setHead
                    p.next = null; // help GC,原先的辅助节点的next指向空
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) && // 当获取锁失败了是否需要阻塞
                    parkAndCheckInterrupt())  // 如果需要Park,使用LockSupport.park()阻塞并且检查中断
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node); // 如果执行失败了,取消获取锁, 把当前线程状态设置为CANCEL
        }
    }

setHead(node) 这行代码看上去有点奇怪,获取锁成功不是应该出队列吗?怎么字面意思好像是还把当前线程设为header Node。那么看看它的具体实现

setHead
private void setHead(Node node) {
        head = node; // head 设置为当前线程
        node.thread = null; // node的线程字段指向null
        node.prev = null; // node的前节点指向null
    }

一目了然吧,这个Node是不是完全符合辅助节点的定义。

shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL) //前继节点还在等待触发,还没当前节点的什么事儿,所以当前节点可以被park
    return true;
    if (ws > 0) { // 前继节点是CANCELLED ,则需要从同步队列中删除,并检测新接上的前继节点的状态,若还是为CANCELLED ,还需要重复上述步骤
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else { //到这一步,waitstatus只有可能有2种状态,一个是0,一个是PROPAGATE,无论是哪个都需要把当前节点的状态设置为SIGNAL,-1
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

等待队列中,正常情况下前继节点的等待状态为SIGNAL

release

直接看源码了

public final boolean release(int arg) {
        if (tryRelease(arg)) { // 尝试释放锁,由子类实现
            Node h = head; // 释放锁成功,h指向head
            if (h != null && h.waitStatus != 0) // head不为空并且head的状态不为0
                unparkSuccessor(h); // unpark,唤醒等待队列,并返回成功
            return true;
        }
        return false; // 释放锁失败,直接返回false
    }
private void unparkSuccessor(Node node) { // node指的是head
        int ws = node.waitStatus;
        if (ws < 0) // 如果当前Node等待状态<0,直接设置为0
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next; // s指向当前Node的下一个Node
        if (s == null || s.waitStatus > 0) { // 如果下一个是空的或者等待状态是CANCELLED
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t; // 从尾部开始从前找,找到最前面的waitStatus<=0的Node
        }
        if (s != null)
            LockSupport.unpark(s.thread); // 唤醒Node中的线程
    }

acquireShared

public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0) // 尝试获取资源
            doAcquireShared(arg); // 没拿到资源,需要等待
    }
private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED); // 以共享模式加入队列尾部
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) { // 自旋
                final Node p = node.predecessor(); // 前置节点
                if (p == head) { // 如果前置为head
                    int r = tryAcquireShared(arg); // 尝试获取资源,返回资源剩余的数量
                    if (r >= 0) { // 拿到资源
                        setHeadAndPropagate(node, r); // 修改head节点
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }

releaseShared 唤醒

在这里插入图片描述
这里有个问题,为什么只唤醒了一次,却能够唤醒等待队列的所有线程?
看了一下jvm源码,调用了unsafe.unpark本地方法。这个方法似乎会重复调用。

void WatcherThread::unpark() {
  MutexLockerEx ml(PeriodicTask_lock->owned_by_self() ? NULL : PeriodicTask_lock, Mutex::_no_safepoint_check_flag);
  PeriodicTask_lock->notify(); // 通知
}
bool Monitor::notify() {
  assert (_owner == Thread::current(), "invariant") ;
  assert (ILocked(), "invariant") ;
  if (_WaitSet == NULL) return true ;
  NotifyCount ++ ;

  // Transfer one thread from the WaitSet to the EntryList or cxq.
  // Currently we just unlink the head of the WaitSet and prepend to the cxq.
  // And of course we could just unlink it and unpark it, too, but
  // in that case it'd likely impale itself on the reentry.
  Thread::muxAcquire (_WaitLock, "notify:WaitLock") ;
  ParkEvent * nfy = _WaitSet ; // 等待队列
  if (nfy != NULL) {                  // DCL idiom
    _WaitSet = nfy->ListNext ; // 等待队列指向下一个元素
    assert (nfy->Notified == 0, "invariant") ;
    // push nfy onto the cxq
    for (;;) {
      const intptr_t v = _LockWord.FullWord ;
      assert ((v & 0xFF) == _LBIT, "invariant") ;
      nfy->ListNext = (ParkEvent *)(v & ~_LBIT);
      if (CASPTR (&_LockWord, v, UNS(nfy)|_LBIT) == v) break;
      // interference - _LockWord changed -- just retry
    }
    // Note that setting Notified before pushing nfy onto the cxq is
    // also legal and safe, but the safety properties are much more
    // subtle, so for the sake of code stewardship ...
    OrderAccess::fence() ;
    nfy->Notified = 1;
  }
  Thread::muxRelease (_WaitLock) ;
  if (nfy != NULL && (NativeMonitorFlags & 16)) {
    // Experimental code ... light up the wakee in the hope that this thread (the owner)
    // will drop the lock just about the time the wakee comes ONPROC.
    nfy->unpark() ; // 如果等待队列不会空,继续调用unpark
  }
  assert (ILocked(), "invariant") ;
  return true ;
}

不知猜想是否正确

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值