一文疏通AQS到ReentrantLock

AQS是什么

AQS全称是AbstractQueuedSynchronizer,是juc下一个中心的抽象类,用于构建各种同步器和锁

比如咱们熟悉的ReentrantLock、ReadWriteLock、CountDownLatch等等是根据AQS

作业原理

下面这张图很清晰了反映了AQS的作业原理

一文疏通AQS到ReentrantLock

当线程获取锁时,即企图对state变量做修正,如修正成功则获取锁;

如修正失败则包装为节点挂载到行列中,等候持有锁的线程开释锁并唤醒行列中的节点

一文疏通AQS到ReentrantLock

首要在AQS里面,有几个中心的组成

●state:同享资源的状况

●以Node节点组成的双端行列——CLH

●两个保护行列的Node节点head和tail

AQS根本的属性(源码)

public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
//头节点
private transient volatile Nodehead;
    //尾节点
private transient volatile Nodetail;
//同步状况
private volatile intstate;
     static final class Node {
    //节点状况
    volatile intwaitStatus;
    //前驱节点
    volatile Nodeprev;
    //后继节点
    volatile Nodenext;
    //当时节点所代表的线程
    volatile Threadthread;
    //等候行列运用时的后继节点指针
    NodenextWaiter;
    }
}

这儿的Node的waitStatus需求留心一下:

该字段共有5种取值:

●CANCELLED=1。节点引用线程因为等候超时或被打断时的状况。

●SIGNAL=-1。后继节点线程需求被唤醒时的当时节点状况。当行列中参加后继节点被挂起(block)时,其前驱节点会被设置为SIGNAL状况,表明该节点需求被唤醒。

●CONDITION=-2。当节点线程进入condition行列时的状况。(见ConditionObject)

●PROPAGATE=-3。仅在开释同享锁releaseShared时仇人节点运用。(见同享锁分析)

●0。节点初始化时的状况

独占锁分析

acquire(int)

public final void acquire(intarg) {
    //tryAcquire需完结类处理
    //如获取资源成功,直接回来
    if (!tryAcquire(arg) && 
        //如获取资源失败,将线程包装为Node增加到行列中堵塞等候
        acquireQueued(addWaiter(Node.EXCLUSIVE),arg))
        //如堵塞线程被打断
        selfInterrupt();
}

acquire中心为tryAcquire、addWaiter和acquireQueued三个函数,其间tryAcquire需详细子类完结。每逢线程调用acquire时都首要会调用tryAcquire,失败后才会挂载到行列,因此acquire完结默以为非公正锁(后到的线程可能会经过这儿的tryAcquire直接拿到锁)

一文疏通AQS到ReentrantLock

一文疏通AQS到ReentrantLock
addWaiter将线程包装为节点node,尾插式参加到行列中,如行列为空,则会增加一个空的头节点。值得注意的是addWaiter中的enq办法,经过CAS+自旋的办法处理尾节点增加冲突。

//sync行列中的结点在独占且疏忽中止的形式下获取(资源)
final boolean acquireQueued(final Nodenode, intarg) {
    //标志
    booleanfailed= true;
    try {
        //中止标志
        booleaninterrupted= false;
        for (;;) { //无限循环
            //获取node节点的前驱结点
            final Nodep=node.predecessor(); 
            if (p==head&& tryAcquire(arg)) { //前驱为头节点而且成功获得锁
                setHead(node); //设置头节点
p.next= null; //helpGC
failed= false; //设置标志
                returninterrupted; 
            }
            if (shouldParkAfterFailedAcquire(p,node) &&
                parkAndCheckInterrupt())//
                //shouldParkAfterFailedAcquire只有当该节点的前驱结点的状况为SIGNAL时,才能够对该结点所封装的线程进行park操作。否则,将不能进行park操作。
                //parkAndCheckInterrupt首要执行park操作,即禁用当时线程,然后回来该线程是否现已被中止
interrupted= true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);//直接把这个节点置空了
    }
}

acquireQueue在线程节点参加行列后判别是否可再次测验获取资源,如不能获取则将其前驱节点标志为SIGNAL状况(表明其需求被unpark唤醒)后,则经过park进入堵塞状况

release(int)

public final boolean release(intarg) {
    if (tryRelease(arg)) { //开释成功
        //保存头节点
        Nodeh=head; 
        if (h!= null &&h.waitStatus!= 0) //头节点不为空而且头节点状况不为0
            unparkSuccessor(h); //开释头节点的后继结点
        return true;
    }
    return false;
}
private void unparkSuccessor(Nodenode) {
    //这儿,node一般为当时线程地点的结点。
    intws=node.waitStatus;
    if (ws< 0)//置零当时线程地点的结点状况,答应失败。
        compareAndSetWaitStatus(node,ws, 0);
    Nodes=node.next;//找到下一个需求唤醒的结点s
    if (s== null ||s.waitStatus> 0) {//假如为空或已取消
s= null;
        for (Nodet=tail;t!= null &&t!=node;t=t.prev) //从后向前找。
            if (t.waitStatus<= 0)//从这儿能够看出,<=0的结点,都是还有效的结点。
s=t;
    }
    if (s!= null)
        LockSupport.unpark(s.thread);//唤醒
}

release流程较为简略,测验开释成功后,即从头结点开端唤醒其后继节点,如后继节点被取消,则转为从尾部开端找堵塞的节点将其唤醒。堵塞节点被唤醒后,即进入acquireQueued中的for(;;)循环开端新一轮的资源竞争

总结

上述内容细心看完,现已能够理解根本的AQS作业原理了,但是咱们的完结类需求完结两个try的办法(因为默许是直接抛异常,这可不可),所以下面的ReentrantLock其实并没有增加很多的锁的细节,根本上用的还是AQS这套锁的流程,无疑便是进行了公正与非公正的分类,以及完结对应的try办法

ReentrantLock的类结构

ReentrantLock是Lock接口的完结类,根本都是经过聚合了一个行列同步器(AQS)的子类完结线程访问控制的

一文疏通AQS到ReentrantLock

一文疏通AQS到ReentrantLock
ReentrantLock完结了Lock接口,有三个内部类,其间Sync承继自AQS,然后两者承继自Sync,它们都承继了AQS的才能。

公正锁与非公正锁

结构函数

//生成一个公正锁
static Locklock= new ReentrantLock(true);
//生成一个非公正锁
static Locklock= new ReentrantLock(false);
static Locklock= new ReentrantLock();//默许参数便是false
public ReentrantLock(booleanfair) {
sync=fair? new FairSync() : new NonfairSync();//FairSync表明公正锁,NonfairSync表明非公正锁
}

详细完结tryAcquire

一文疏通AQS到ReentrantLock

一文疏通AQS到ReentrantLock
一文疏通AQS到ReentrantLock

一文疏通AQS到ReentrantLock
其公正的中心办法:

    /*判别等候行列中是否存在等候中的线程*/
    public final boolean hasQueuedPredecessors() {
        //Thecorrectnessofthisdependsonheadbeinginitialized
        //beforetailandonhead.nextbeingaccurateifthecurrent
        //threadisfirstinqueue.
        Nodet=tail; //Readfieldsinreverseinitializationorder
        Nodeh=head;
        Nodes;
        //判别是否存在先等候的线程详细分析如下
        returnh!=t&& ((s=h.next) == null ||s.thread!= Thread.currentThread());
    }

公正锁和非公正锁的lock()办法仅有的差异就在于公正锁在获取同步状况时多了一个限制条件:hasQueuedPredecessors()—–公正锁加锁时判别等候行列中是否存在有效节点的办法

tryRelease

    protected final boolean tryRelease(intreleases) {
        intc= getState() -releases;
        //获取独占锁的线程才能够开释线程
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        booleanfree= false;
        //state==0表明所有锁已开释
        if (c== 0) {
free= true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        returnfree;
    }

总结

ReentrantLock完结了Lock接口,有三个内部类,其间Sync承继自AQS,然后两者承继自Sync,它们都承继了AQS的才能。

本质上来说ReentrantLock的底层原理便是AQS。

在Sync的两个子类FairSync和NonfairSync分别是公正锁战略和非公正锁战略的完结(公正锁多了一个查看是否有其他等候线程的条件)。然后完结了不同的tryAcquire(intacquires),然后在线程测验获取锁时,执行不同的战略

参阅:

Java 多线程并发 【10】ReentrantLock – 掘金 ()

10分钟从源码级别搞懂AQS(AbstractQueuedSynchronizer) – 掘金 ()

【后端面经-Java】公正锁和加锁流程 – 掘金 ()