多线程之AQS学习

本文内容为个人参照JKD1.8源码注释及资料学习后个人的理解总结,理解错误之处还望勿喷。

1.AQS是什么?
多线程并发访问共享资源时,为了保证数据的安全性,我们采用加锁的手段保证多线程访问共享资源的安全(这里不讨论无锁手段)。锁可以大致分为两种----Synchronized锁和基于Lock,AQS的实现锁。AQS是一个抽象队列同步器(Abstract Queued Synchronizer),用来控制多线程对共享资源的同步操作。常见的锁如:ReentrantLock, ReentrantReadWriteLock,CountDownLatch,StampedLock等都是基于AQS实现的锁。

JDK1.8中注释为:AQS是一个基于FIFO等待队列、用于实现阻塞锁和相关同步器的框架。该类被设计目的是作为其他同步器的基础,这些同步器使用原子类整数代表状态(翻译的不好,将就看)。

 * Provides a framework for implementing blocking locks and related
 * synchronizers (semaphores, events, etc) that rely on
 * first-in-first-out (FIFO) wait queues.  This class is designed to
 * be a useful basis for most kinds of synchronizers that rely on a
 * single atomic {@code int} value to represent state.

AQS抽象类中定义了一个final静态的Node节点和一些原子类的CAS方法方法,如下:
在这里插入图片描述
在这里插入图片描述
2.AQS类伪代码摘要
AQS在其内部维护了一个Node对象,用于保存竞争竞争同步器失败的线程。同时提供了一些竞争获取同步器方法,这个CAS方法都是基于unsafe的原子方法。

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer  implements java.io.Serializable {
	
	//Node节点内部类
	static final class Node {
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;
        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        Node nextWaiter;
		
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }
    }
	
	//竞争获取同步器方法伪代码摘要
	public final void acquire(int arg) {}
	final boolean acquireQueued(final Node node, int arg) {}
	private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {}
	private void cancelAcquire(Node node) {}
	private Node enq(final Node node) {}
	private final boolean compareAndSetTail(Node expect, Node update) {}
	private final boolean compareAndSetTail(Node expect, Node update) {}
	
}

3.Node代码学习
以下注释,翻译摘抄自JDK1.8源码注释,刚学习翻译的不准确!!!Node对象定义了不同的状态和方法,用于封装竞争同步器的线程对象。

static final class Node {
       //node处于共享模式
        static final Node SHARED = new Node();
        //node处于独占模式
        static final Node EXCLUSIVE = null;
        //该状态表明线程被取消了
        static final int CANCELLED =  1;
        //该状态表明后继线程需要被唤醒
        static final int SIGNAL    = -1;
        //该线程等待在Condition对象上
        static final int CONDITION = -2;
        //该状态表明下一个共享模式获取应该无条件传播
        static final int PROPAGATE = -3;
        
        //等待状态:SIGNAL、CANCELLED、CONDITION、PROPAGATE、0
        //SIGNAL:当前节点的后继节点处于阻塞状态(经过park),因此当前节点释放了同步器或者取消后必须唤醒它的后   继节点,为了避免多线程竞争,acquire方法首先需要一个SIGNAL,然后尝试去原子性获取,失败后就阻塞入队。
        //CANCELLED:当前节点取消,这是一个终态,该状态下的线程不会再次出现在阻塞队列中。
        //CONDITION:当前节点处于CONDITION上,在该状态发生转变前该节点不会作为同步节点。
        //PROPAGATE:一个释放的共享状态应该通知传播给所有节点。
        //0:非以上状态
        volatile int waitStatus;

         //前一个节点
        volatile Node prev;
        //下一个节点
        volatile Node next;
        //当前线程
        volatile Thread thread;
        //下一个等待节点,与处于condition上的节点关联。
        Node nextWaiter;
		//是否为共享模式
        final boolean isShared() {
            return nextWaiter == SHARED;
        }
        //返回前驱节点
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }
    }

4.AQS中重要方法学习。
4.1 获取同步器方法acquire(int args)
以独占模式获取同步器,忽略中断。获取同步器成功时,tryAcquire至少会调用一次。获取同步器失败时,线程会加入到队列中,并且可能会在阻塞和非阻塞状态间多次切换,直到成功获取到同步器。

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

4.1.1 tryAcquire(int arg)
AQS未对tryAcquire(int arg)方法做具体实现,仅仅抛了一个异常。具体的实现由继承的子类复写。

 protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

在这里插入图片描述

4.1.2 tryAcquire(int arg) 的复写实现方法
我们以AQS的子类Sync(本次看的Sync维护在ReentrantLock类中)同步器中的非公平方法nonfairTryAcquire(acquires)为例看下tryAcquire的实现逻辑。

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            //获取同步器的状态,初始状态为0
            int c = getState();
            if (c == 0) {//state为0表示同步器未被占用,即当前线程可以抢锁
                if (compareAndSetState(0, acquires)) {//CAS去尝试设置state
                    setExclusiveOwnerThread(current);//如果CAS设置state成功,则标记当前线程独占锁
                    return true;
                }
            }
            //如果同步器已被抢占,检测是否是当前线程自身占用(ReentrantLock允许重入)。
            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;//竞争锁失败
        }

4.2 抢占同步器失败
线程抢占同步器失败时,会调用如下acquireQueued(Node node)方法将当前线程对象封装成一个Node节点添加到同步器队列链表中(FIFO)。

acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

4.2.1 addWaiter(Mode) 封装线程为Node

 private Node addWaiter(Node mode) {
        //以给定模式将当前线程封装成一个Node对象
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        //尝试快速入队(队列已存在),Node设置到链表尾部
        Node pred = tail;//取出尾节点pred
        if (pred != null) {//尾节点存在的话
            node.prev = pred;//将当前Node的前驱节点指向尾节点,即pred
            if (compareAndSetTail(pred, node)) {//对比尾节点pred和内存中的预期值,如果一致,则CAS将node设置为新的尾节点
                pred.next = node;//旧的尾节点的后继节点设置为当前节点node
                return node;
            }
        }
        //如果快速入队失败(队列不存在或者抢占失败)
        enq(node);
        return node;
    }

4.2.2 快速入队失败后入队enq(Node node)

 private Node enq(final Node node) {
        for (;;) {//自旋,等于while(true){...}
            Node t = tail;//尾节点
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))//尾节点为null,cas创建一个新Head头节点,
                    tail = head;//头节点赋给尾节点,新创建的节点既是头节点也是尾节点
            } else {//将node节点追加到尾节点t后面
                node.prev = t;//当前节点node的前驱节点指向尾节点t
                if (compareAndSetTail(t, node)) {//如果尾节点t当前值和内存中预期值一直,则将当前节点node设置为新的尾节点
                    t.next = node;//旧的尾节点t的后继节点指向新的尾节点node
                    return t;
                }
            }
        }
    }

4.3 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
4.1方法线程抢占同步器,如果失败则会走4.2方法将线程对象封装成Node节点 ,然后走4.3方法acquireQueued(Node node,arg)尝试从队列中获取节点。

   final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {//自旋
                final Node p = node.predecessor();//当前节点node的前驱节点p
                if (p == head && tryAcquire(arg)) {//前驱节点p为head && 尝试获取同步器,将状态设置为arg
                    setHead(node);//当前节点node设置为头节点
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&  //获取同步器失败后应该park(休眠)
                    parkAndCheckInterrupt()) // 休眠检测中断
                    interrupted = true;//中断
            } 
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

4.4 shouldParkAfterFailedAcquire(Node pred, Node node)

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {//pred前驱节点,node当前节点
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
             //前驱节点pred的waitStatus为SIGNAL,表明当前节点已经设置了请求pred释放同步器时唤醒自己,node可以安全park了。
            return true;
        if (ws > 0) {
             //waitStatus>0,前驱节点被取消,跳过前驱节点
            do {
                node.prev = pred = pred.prev;//1.跳过前驱,取出前驱节点的前驱。2.将当前节点node的前驱指向新的前驱节点。
            } 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.
             */
             //前驱节点的waitStatus必须是0或PROPAGATE,也就是说当前节点park前需要得到一个前驱节点SIGNAL信号。
             //当前节点在park之前会尝试确保当前节点不需要再去获取同步器。
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//CAS设置前驱节点pred的状态为SIGNAL
        }
        return false;
    }

5.释放同步器
前面步骤看了获取同步器,接下来跟踪学习下释放同步器过程。
AQS中该方法没有具体实现,看下子类实现。

protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

5.1 release(int arg) 的一个实现
这里也同样看下ReentranLock中的静态内部类Sync实现,该类继承自AQS,子类复写了tryRelease(int arg)方法。

   public final boolean release(int arg) {
        if (tryRelease(arg)) {//尝试释放同步器
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);//唤醒后继节点
            return true;
        }
        return false;
    }

5.2 tryRelease(int releases)

 protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())//获取同步器和释放同步器应该是同一个线程对象
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {//0状态代表同步器未被占用
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);//state设置为新值。这里分析ReentranLock是支持重入的,取决于AQS子类的具体实现。
            return free;
        }

5.3 unparkSuccessor(Node node) 唤醒当前节点后继节点

    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0) // -1:后继节点等待唤醒,-2:当前节点等待在condition上,-3:propagate
            compareAndSetWaitStatus(node, ws, 0);//CAS将当前节点waitStatus置为0,即别的节点可以来抢占同步器了。

        Node s = node.next;//后继节点
        if (s == null || s.waitStatus > 0) {//后继节点为null,或者后继节点已取消
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev) //逆序查找最后一个waitStatus<0 的节点
                if (t.waitStatus <= 0)
                    s = t;//将其设置为当前节点(即head节点)的后继节点
        }
        if (s != null)
            LockSupport.unpark(s.thread);//真正唤醒方法是调用Native方法
    }

总结:AQS的原理图如下:
在这里插入图片描述

至此为止,AQS同步器的换取和释放过程原理,大概粗略学习过了一下。基于AQS的有一些已经实现好的锁方法,如:ReentrantLock,ReetrantReadWriteLock,Semaphore,CountDownLatch,ThreadPoolExecutor等。因此,掌握了AQS的基本原理后再学习这些实现框架就会轻松很多,甚至可以写一个自己的锁。

最后,本文内容来自于个人学习JDK中AQS源码后个人感悟总结,有理解错误指出还望口下留情,共同学习。

附:ReentranLock部分代码
下面一段摘自JDK1.8的ReentranLock的部分代码,可以看到ReentrantLock内部维护了一个Sync类,该类extends AQS。ReentrantLock的加锁方法lock(),释放锁unlock()方法均来自于AQS。

public class ReentrantLock implements Lock, java.io.Serializable {

    private final Sync sync;

    //静态内部类,同步器
    abstract static class Sync extends AbstractQueuedSynchronizer {  //继承自AQS
    }

    //静态内部类,非公平同步器
    static final class NonfairSync extends Sync {
    }

    //静态内部类,公平同步器
    static final class FairSync extends Sync {
    }

    //构造一个非公平的同步器
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    //根据传入条件,构造一个相应的同步器
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

    public void lock() {
        sync.lock();
    }

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

    public void unlock() {
        sync.release(1);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值