JAVA------JUC源码探究之AbstractQueuedSynchronizer(AQS)

 概念

        首先,AQS是什么?

在官网的解释中,AQS是一个基于 先进先出(FIFO)队列,可以构建其他同步装置(semaphores,events)的基础框架。为大多数依赖于用一个原子int值来表示状态的同步器提供了基础。使用方法为继承,子类通过继承,必须实现父类中的抽象方法来管理其状态。而一般子类是作为同步装置中的内部类,如ReetrantLock中的Sync,AQS不实现任何同步接口,只是提供一些公共的操作方法,各个锁或者同步装置通过调用这些方法来实现自己的设计.

 AQS默认支持独占模式和共享模式,当以独占方式获取时,其他线程无法获取成功;以共享模式获取时,其他线程可能成功,当线程获取锁成功时,还必须确定下一个等待线程(如果有的话)能否获取成功,如ReetrantReadWriteLock中的readLock。                                                                                  

源码分析

      队列节点Node数据结构

AQS中依赖的是一个队列结构,将等待线程封装在队列节点中,节点结构主要的成员变量如下:

class Node {
        //等待状态
        volatile int waitStatus;
        //前置节点
        volatile Node prev;
        //下一节点
        volatile Node next;
        //等待线程
        volatile Thread thread;
        //下一等待线程,condition队列
        Node nextWaiter;
}

注意的是,waitStatus的状态有其特定的值:

SIGNAL(-1)后继节点中的线程需要被唤醒(unpark)
CANCELLED(1)由于超时或中断,该节点被取消。 节点永远不会离开此状态。 特别是,具有取消节点的线程永远不会再次阻塞。
CONDITION(-2)该节点当前在条件队列中。
PROPAGATE(-3)后续的acquireShared能够得以执行
0表示当前节点在sync队列中,等待着获取锁。

        AQS数据结构

主要成员变量:

  private transient volatile Node head;//头结点
  private transient volatile Node tail;//尾结点
  private volatile int state;//状态

 链式结构示意如下:

使用state字段来记录锁状态,并提供三个方法来操作state值:

    protected final int getState() {
        return state;
    }
    protected final void setState(int newState) {
        state = newState;
    }
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

 AQS可拓展的几种API

AQS提供5个抽象方法,由继承AQS的子类(如ReentratLock中的Sync)来具体实现

boolean tryAcquire(int arg)以独占模式尝试进行获取 状态,若 状态 未被占用,则可以获取,否则不能获取
boolean tryRelease(int arg)释放锁
boolean tryAcquireShared(int arg)以共享模式尝试进行获取。 此方法应查询对象的状态是否允许以共享模式获取对象,如果允许则获取对象。
boolean tryReleaseShared(int arg)释放锁
boolean isHeldExclusively()独占模式下,状态是否被占用

独占模式为例,其核心的执行逻辑如下:

     //独占模式请求锁
     while (!tryAcquire(arg)) {//尝试获取锁
        //未获取到锁
           //1: 若当前线程未入等待队列,则加入等待队列
           //2:阻塞当前线程       
     }

     //独占模式释放锁
     if (tryRelease(arg))//尝试释放锁
        //唤醒等待队列中的线程
     

看一下官网给出的示例

class Mutex implements Lock, java.io.Serializable {

   // 自定义的同步器,继承AQS
   private static class Sync extends AbstractQueuedSynchronizer {
     // Reports whether in locked state
      //是否处于被占用状态
     protected boolean isHeldExclusively() {
       return getState() == 1;
     }

     // Acquires the lock if state is zero
    //当状态为0时候获取锁
     public boolean tryAcquire(int acquires) {
       assert acquires == 1; // Otherwise unused
       if (compareAndSetState(0, 1)) {//CAS机制设置state值
          //设置当前线程为独占线程
         setExclusiveOwnerThread(Thread.currentThread());
         return true;
       }
       return false;
     }

     // Releases the lock by setting state to zero
     //释放锁,将状态置为0
     protected boolean tryRelease(int releases) {
       assert releases == 1; // Otherwise unused
       if (getState() == 0) throw new IllegalMonitorStateException();
        //将独占线程设为null
       setExclusiveOwnerThread(null);
       setState(0);
       return true;
     }

     // Provides a Condition
     Condition newCondition() { return new ConditionObject(); }

     // Deserializes properly
     private void readObject(ObjectInputStream s)
         throws IOException, ClassNotFoundException {
       s.defaultReadObject();
       setState(0); // reset to unlocked state
     }
   }

   // The sync object does all the hard work. We just forward to it.
   private final Sync sync = new Sync();

   public void lock()                { sync.acquire(1); }
   public boolean tryLock()          { return sync.tryAcquire(1); }
   public void unlock()              { sync.release(1); }
   public Condition newCondition()   { return sync.newCondition(); }
   public boolean isLocked()         { return sync.isHeldExclusively(); }
   public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
   public void lockInterruptibly() throws InterruptedException {
     sync.acquireInterruptibly(1);
   }
   public boolean tryLock(long timeout, TimeUnit unit)
       throws InterruptedException {
     return sync.tryAcquireNanos(1, unit.toNanos(timeout));
   }
 }

 AQS提供的常用公共方法

 AQS对也提供了几种可以直接调用的方法       

void acquire(int arg) :在独占模式下获取,忽略中断。 通过至少调用一次tryAcquire(int)来实现,并在成功后返回。 否则,线程将排队,并可能反复阻塞和解除阻塞,并调用tryAcquire(int)直到成功。    

此方法可用于实现Lock.lock()方法。

源代码:

    public final void acquire(int arg) {
            //tryAcquire(arg)由具体子类实现
        if (!tryAcquire(arg) &&
            //加入等待队列,不断尝试获取
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

 方法执行逻辑如下:

  1. 调用子类实现的tryAcquire()方法,若成功则直接返回;
  2. 若调用失败,则构造Node节点,将当前线程封装入节点中并加入等待队列;
  3. 在队列中循环等待,直到被调度;

构造Node并加入等待队列的具体实现代码如下:

    //加入等待队列
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        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;
                }
            }
        }
    }

 通过分析源码可知,执行逻辑如下:

  1. 将当前线程封装入Node节点中;
  2. 将node加入等待队列中;

加入等待队列操作如下:

  1. 找到尾结点;
  2. 若尾结点存在,则将当前节点的前置节点指向当前尾结点( node.prev = pred),将tail的值修改当前节点(compareAndSetTail(pred, node)(此步骤为原子操作,直接操作内存),若修改成功,则将尾结点的后置指向当前节(pred.next = node);
  3. 若尾结点不存在,或者修改tail失败,若是尾结点为null ,此时队列为空,则分配一个头节点head(原子操作),并将尾结点tail指向头结点。而后重复操作2,直到成功;

那么为什么要在一个for循环体内?

      AQS允许多个线程同时执行,但需要保证操作的原子性。基于CAS操作,需要配合死循环使用。这能够保证每个线程都能够正确的进入等待队列。

 线程在队列中等待(阻塞),代码如下

//独占模式下且不间断的获取锁
    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);
        }
    }

执行逻辑如下:

  1. 获取当前节点的前驱节点p;
  2. 若p为头结点head,表示当前线程已是队列中的第一个线程,并尝试获得锁。若成功,则将当前节点置为新的头结点(节点内的pre,thread等成员变量置为空),并将当前头结点中的next置为空(此时旧的头结点head已从队列中脱离,并由垃圾回收器回收);
  3. 根据node中前置节点中的的waitStatus字段值,当值为SIGNAL时,当前线程阻塞,因为SIGNAL表示当前置节点释放锁时,会唤醒后继节点中的线程;
  4. 若当前线程不阻塞,循环执行步骤2;

void acquireInterruptibly(int arg):以独占模式获取,如果线程被中断则中止。 通过首先检查中断状态,然后至少调用一次 次tryAcquire(int)来实现,并在成功后返回。 否则,将线程排队,并可能反复阻塞和解除阻塞,并调用tryAcquire(int)直到成功或线程被中断为止。

此方法可用于实现Lock.lockInterruptible()方法。

源代码:

    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
       //检查中断状态
        if (Thread.interrupted())
        //抛出异常
            throw new InterruptedException();
        //调用具体子类实现
        if (!tryAcquire(arg))
        //失败则进入队列
            doAcquireInterruptibly(arg);
    }

执行逻辑如下:

  1. 检查线程中断状态,若被中断,则抛出异常,结束;
  2. 调用具体子类实现tryAcquire(int)方法获取,若成功,则返回,结束;
  3. 若获取失败,则进入等待队列等待;

此方法在执行步骤上与acquire()类似,然而不同的是,在此方法中可以接受中断信号,对于外界中断能够提前结束获取state的操作。以下是具体实现:

     private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        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;
                }
              
                if (shouldParkAfterFailedAcquire(p, node) &&
                      //检测中断,抛出异常
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

此方法与acquire方法调用的acquireQueued类似,不同点在于此方法若检测到线程被中断,不再阻塞,提前结束获取state操作,并抛出InterruptedException异常提醒用户。


boolean tryAcquireNanos(int arg, long nanosTimeout):尝试以独占方式进行获取,如果被中断则中止,如果给定的超时时                                                                                                    间过去,则失败。

通过首先检查中断状态,然后至少调用一次tryAcquire(int)来实现,并在成功后返回。 否则,将线程排队,调用tryAcquire(int)直到成功或线程被中断或超时为止。

此方法可用于实现方法Lock.tryLock(long,TimeUnit)。

源代码:

     public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }

执行逻辑:

  1. 检查线程中断状态,若中断,抛出异常,结束;
  2. 尝试调用子类实现的tryAcuqire()尝试获取状态,若成功,直接返回;
  3. 若失败,进入等待队列,若超过nanosTimeout时间仍未获取状态,返回失败;

doAcquireNanos源代码如下:

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

分析源代码,doAcquireNanos()执行逻辑如下:

  1. 若剩余时间<0,返回false;
  2. 计算结束时间,当前时间+剩余时间;
  3. 构造Node节点,封装当前线程,加入等待队列;
  4. 满足获取状态条件,并尝试获取,若成功,则返回;
  5. 若获取状态失败或不满足获取条件,将此线程休眠一段时间;

注意的是,当剩余时间小于 spinForTimeoutThreshold(1000L纳秒)时,进入自旋过程;


boolean release(int arg):以独占模式释放。 如果tryRelease(int)返回true,唤醒一个或多个等待线程。

此方法可用于实现Lock.unlock()方法。

源代码:

     public final boolean release(int arg) {
        //子类实现的tryRelease()方法
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

      执行逻辑如下:

  1. 调用子类实现的tryRelease(arg),释放state;
  2. 唤醒后继节点中的线程;

唤醒后继线程源代码如下:

  //唤醒node节点的后继节点中的线程,如果存在    
  private void unparkSuccessor(Node node) {
      
        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. 找到那个当前节点的后置节点s;
  2. 若s为空,表示无后续线程,结束;
  3. 若s不为空,且s节点的状态为CANCEL(取消),则从队列尾结点开始往前寻找第一个符合唤醒条件(waitStatus<=0)的节点,并将其置为s;
  4. 若此时s不为空,则唤醒s节点中的线程;

void  acquireShared(int arg):以共享模式获取,忽略中断。

通过首先至少调用一次tryAcquireShared(int)来实现,并在成功后返回。 否则,将线程排队,并可能反复阻塞和解除阻塞,并调用tryAcquireShared(int)直到成功。

源代码:

 public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

首先调用具体子类实现tryAcquireShared(int),若失败,则进入doAcquireShared(arg)中排队等待,doAcquireShared方法源代码为:

private void doAcquireShared(int arg) {
        //构造node节点,封装当前线程,并加入等待队列
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
        //获取当前节点的前置节点p
                final Node p = node.predecessor();
        //当p为头结点,此时当前节点可以开始获取状态
                if (p == head) {
        //尝试获取状态,若r<0,则表示失败,r=0,表示后续没有可以共享获取的线程,r>0,表示后续还有可以共享的线程
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
        //重新设置头结点,并通知下一节点获取状态,如果有的话
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
        //将当前线程暂停,减少开销
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

执行逻辑为:

  1. 构造node节点,加入等待队列;
  2. 获取当前节点的前置节点p,若p不是头结点,则当前线程阻塞,以减少for循环开销;
  3. 当p变为头结点时,当前线程可获得锁,则调用具体子类实现tryAcquireShared()方法,若成功,则更新头结点,并且通知下一节点获取锁。

更新头结点和通知下一节点是调用setHeadAndPropagete()方法,源代码为:

  private void setHeadAndPropagate(Node node, int propagate) {
        //设置头结点
        Node h = head; // Record old head for check below
        setHead(node);
        //当存在后继节点,且等待状态<0时,唤醒线程,尝试获取锁
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

 

boolean realseShared(int arg): 以共享模式释放状态;

源代码:

 public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

执行逻辑:

  1. 调用子类实现的tryReleaseShared(arg)方法;
  2. 成功后,唤醒后续节点;

步骤2调用doReleaseShared方法,源代码如下:

private void doReleaseShared() {
       
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

其核心是当满足调用条件,即调用unparkSuccessor(h)方法,唤醒下一节点线程。

总结

   AQS是依赖一个int类型的成员变量state来保存锁的状态,并提供对state进行原子操作的三个方法。并且对于不同的模式也提供了不同的操作方法,两种模式(独占模式,共享模式)都对应有阻塞式获取,可被打断的阻塞获取,带有过期时间的阻塞获取(acquire),两种模式也有其对应的释放方法(release)。他们都需要调用AQS中的抽象方法,如tryAcquire...,这些方法为非阻塞式,但是需要由继承的子类根据其设计逻辑来具体实现。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值