zoukankan      html  css  js  c++  java
  • 初步认识AQS

    什么是AQS?

    AQS是AbstractQueuedSynchronizer类的简称,位于java.util.concurrent.locks下。直译为抽象队列同步器,一般简称为同步器。

    AQS维护了一个volatile int类型的stateFIFO的虚拟双向队列,当多线程竞争资源的时候未获得资源的线程由FIFO的虚拟双向队列维护。

    其核心思想是:如果被请求的共享资源空闲,则将当前线程设置为有效的工作线程看,并锁定共享资源。如果被请求的共享资源被占用则需要一套线程阻塞及被唤醒时资源分配的机制。这个机制由CLH队列实现。

    • AQS其实就是一个可以给我们实现锁的框架
    • 内部实现的关键是:先进先出的队列、state状态
    • 定义了内部类ConditionObject
    • 拥有两种线程模式
      • 独占模式
      • 共享模式
    • 在LOCK包中的相关锁(常用的有ReentrantLock、 ReadWriteLock)都是基于AQS来构建

    来源:

    AQS有什么?

    简单看看AQS里面最重要的state和队列,看看源码是怎么样的(只选取了最核心的代码)

    同步状态
    private volatile int state;
    
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
    

    使用volatile保证了可见性,使用CAS保证了原子性。

    CLH队列

    CLH队列实际上是一个虚拟双向队列

    private transient volatile Node head;
    private transient volatile Node tail;
    
    static final class Node {
        static final Node SHARED = new Node();//共享方式
        static final Node EXCLUSIVE = null;//独占方式
        static final int CANCELLED =  1;//线程已取消(不会被AQS调度)
        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;
    }
    

    如图

    acquire方法

    获得锁的过程就是acquire方法实现的,这里用到了模板设计模式:tryAcquire(arg)由具体的子类实现。这里以ReentrantLock的公平锁(FairSync)为例。

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&//尝试获得锁
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//获得锁失败,将当前线程打包为Node并维护CLH队列
            selfInterrupt();
    }
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {//没有线程持有锁
            if (!hasQueuedPredecessors() &&//判断是否需要排队
                compareAndSetState(0, acquires)) {//不需要排队,以CAS方式尝试获取资源
                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;
    }
    /**
     *判断是否需要排队
     */
    public final boolean hasQueuedPredecessors() {
        Node t = tail; 
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    /*	
    	if(h != t){//判断队列中的结点是否至少有两个以上
        	s = h.next;//获得头结点所指向的元素
        	return s.thread != Thread.currentThread();//判断当前线程是否为头结点所指向的元素,如果是的返回false不需要排队,不是则返回true需要排队
    	}else{//队列为空,或者只有头结点,则需要排队
    		return false;
    	}
    */
    }
    //addWaiter(Node.EXCLUSIVE)
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);//将当前线程打包为Node
        Node pred = tail;
        if (pred != null) {//将新结点添加到CLH队尾
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //若队列为空,或CAS添加到队尾失败
        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;
                }
            }
        }
    }
    
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {//自旋直至当前线程获得锁或被park();
                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) &&//判断前驱结点是否为SIGNAl状态,若不是将其设置为SIGNAL状态
                    parkAndCheckInterrupt())//park()当前线程
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            return true;
        //简化理解将核心业务代码以外的代码注释
        //if (ws > 0) {
        //    do {
        //        node.prev = pred = pred.prev;
        //    } while (pred.waitStatus > 0);
        //    pred.next = node;
        //} else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//将前驱结点设置为SIGNAL
        //}
        return false;
    }
    

    执行流程图

    总结:

    • 获得锁:当前线程不需要排队(我前面没有结点,或只有头结点)且获得资源成功(我直接获取到资源或者刚好头结点执行完成我获取到资源)
    • park:CLH队列中有一个以上在排队的线程(若头结点为1号结点,我前面至少还有2号结点在排队)或获得锁失败(我就是2结点,但1号结点的线程还在执行我获取锁失败)

    ReentrantLock的FairSync执行流程

    调用lock()实质上调用的是AQS的acquire()方法,在acquire()方法中会首先调用tryAcquire()尝试获得锁,这里用到了模板设计模式是一个模板方法,具体是由子类RenntrantLock实现的。
    tryAcquire()尝试过得锁过程如下:首先会先判断state是否等于0。不等于0:说明有人持有锁,判断持有锁的线程是否为当前线程,若是当前线程则state加1,这就是锁的可重入。若为0:说明没有线程持有资源,这里由于是公平锁,会先判断自己是否需要排队。若CLH队列为空,或前序结点为head结点,则说明不需要排队尝试获得锁,获得成功执行同步代码块。
    获得锁失败后将当前线程以独占方式打包为一个Node,然后以CAS的方式将其添加到CLH队列尾部。接着会再一次尝试获得锁,获得锁成功执行同步代码块,获得锁失败将前驱结点的waitStatus设置为SIGNAL,然后调用park()将本线程挂起。

    简化版

    先尝试过得锁,获得锁成功state加一当前线程持有锁,执行同步代码;获得锁失败将当前线程包装为Node添加到CLH队尾,然后会判断当前节点有木有获得锁的权利,有尝试获得锁;没有将其前驱结点的waitStatus设置为SIGNAL,然后将当前线程park

    release方法
    public final boolean release(int arg) {
        if (tryRelease(arg)) {//尝试释放锁
            //完全释放了锁
            Node h = head;
            if (h != null && h.waitStatus != 0)//头结点不为空且waitStatus被修改过
                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;
    }
    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) {//只有头结点或者头结点的下一个结点处于CANCELLED(已取消,处于这个状态的结点是不会被AQS进行调度的)状态 
            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);
    }
    

    release释放锁的时候,首先会调用tryRelease方法来释放锁(实际上就是state减一)。当完全释放锁(state等于0)时会选择一个合适的线程来唤醒。一般情况下会释放头结点的下一个结点对应的线程,当头结点的下一个结点的waitStatus>0即处于CANCELLED(已取消不会被AQS调度)状态,则会从尾结点从后往前找waitStatus<=0的结点(被AQS调度的结点)如果找到了将其唤醒(unpark)。

  • 相关阅读:
    从万元户到千万富翁:6招助你蜕变
    16款有助于提升工作效率的工具
    8个身家百万的儿童创业者
    关于航模无刷电机发热问题的假想解决方案
    折腾了2个晚上无刷电调(ESC),电机终于转起来了,特此记录一下
    PWM占空比和分辨率(转)
    MSB与LSB(转)
    树莓派3uart wifi模块调试 (浪费了我3天时间的宝贵经验)
    USB加minicom使用串口
    【转】使用BBB的device tree和cape(重新整理版)
  • 原文地址:https://www.cnblogs.com/shaoyu/p/14710512.html
Copyright © 2011-2022 走看看