AQS源码解析及原理总结

AQS是java中concurrent包下的Lock的实现,是AbstractQueuedSynchronizer抽象队列同步器的简称,主要作用就是来保证多线程环境下,代码的同步执行。和synchronize的作用一样,实现不一样。synchronize是操作系统级别的锁,而AbstractQueuedSynchronizer是java代码级别的锁的实现。

这篇博文主要是解析ReentrantLock下公平锁的源码,并总结器实现原理。

1. 调用sync的lock()方法,sync的实现有公平锁和非公平锁。

 2. 公平锁的lock实现,调用 acquire(1)。

 3. 跟进2中的 acquire。

 4. 跟进 tryAcquire(arg)。

protected final boolean tryAcquire(int acquires) {
     final Thread current = Thread.currentThread();
     int c = getState();
     if (c == 0) { //为0,表明锁还没有被其它线程占有
         //hasQueuedPredecessors 再次确定持有AQS队列中的头和尾node是否相等,首次进来都为空,所以hasQueuedPredecessors()返回false
         //compareAndSetState在hasQueuedPredecessors返回false后,通过cas修改锁状态为state+1.
        if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
            //设置AQS当前持有锁的线程
            setExclusiveOwnerThread(current);
            return true;
        }
     }
     else if (current == getExclusiveOwnerThread()) {//如果AQS中锁状态不为0,且当前线程和锁持有的独占线程相等,表明这是同一个线程多次重入一个锁,然后修改状态为state+acquires
        int nextc = c + acquires;
        if (nextc < 0)
           throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
     return false;
}

4中代码tryAcquire方法中主要做的事:尝试获取锁,判断AQS中的锁状态state和exclusiveOwnerThread,根据判断结果确定锁是否被其它线程占有,如果没有被占有,获取成功,返回true,反之返回false。

具体分析:

假设一个线程 A

        4.1 线程 A 调用了lock,进入tryAcquire 获取AQS中的state,判断是否为0,如果state为0,表明当前没有其他线程持有这个锁。

                4.11 判断hasQueuedPredecessors() ,虽然state=0了,但是需要二次确定一下是否这个锁没有被其它线程持有,这个代码主要是再次确定一下当前锁是否被其它占用。

t != h:表明等待队列中有其它线程,且锁已经被占用。如果这个锁没有被其它线程持有,那么t和h都为null,所以首次抢占锁时,这个方法return false。

                 4.12 判断compareAndSetState修改AQS状态值state是否能够成功,这个方法主要是在4.11确定锁没有被其它线程持有后,通过cas原子操作修改AQS的锁的状态值为state+1。

 

 通过4.11和4.12判断,如果这个锁没有被其它线程持有,且修改了AQS的锁状态,并且通过setExclusiveOwnerThread(current) 设置AQS中的锁持有线程为当前线程。然后 4 步骤中的tryAcquire方法会返回true。

        4.2 getState()不为0,判断current == getExclusiveOwnerThread()是否为true。如果AQS中锁状态不为0,且当前线程和锁持有的独占线程相等,表明这是同一个线程多次重入一个锁,然后修改状态为state+acquires。

5. 4执行完后可以返回3中的代码。

3中的代码如下:

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

 所以tryAcquire(arg)返回为true时,表明抢到了锁,退出这个方法,执行当前线程的主业务逻辑。如果返回为false时,那么接着执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法。

6. 跟进addWaiter(Node.EXCLUSIVE)。

addWaiter方法中主要做的事情就是把当前线程包装成 Node 对象插入到线程等待队列中,然后返回当前线程的Node节点对象。

private Node addWaiter(Node mode) {
        //用Node包装当前线程
        Node node = new Node(Thread.currentThread(), mode);
        //获取尾部节点
        Node pred = tail;
        if (pred != null) {//如果尾部线程节点不为空,则表明AQS队列中有等待执行的线程
            node.prev = pred;//设置当前线程的前置节点为以前的尾部线程 ------1
            if (compareAndSetTail(pred, node)) {//设置当前线程为新的尾部线程 -----2
                pred.next = node;//设置以前的尾部节点的后置节点为当前线程 ----3
                return node;
            }
        }
        //如果pred == null 或者 compareAndSetTail(pred, node) 为false时,执行以下程序
        enq(node);
        return node;
    }
//通过自旋的方式重复的是上面代码 addWaiter 中的1、2、3过程,目的是把当前线程节点对象插入到线程等待队列。
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;
                }
            }
        }
    }

7. 跟进acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

acquireQueued这个方法的主要做的事情是:线程节点Node对象插入AQS队列后,通过自旋方式不断的判断Node节点的前置节点是否是头节点,因为当前时刻正在执行的是头节点线程的业务逻辑,如果队列中的某一个节点判断自己的前置节点是头节点,那么将要执行的线程就是它,那么它会不断的尝试获取锁资源,一旦头节点的业务执行完毕后会释放锁资源,那么前置节点是头节点的这个节点就会获取到锁资源,并且设置自己为头节点,然后执行自己线程的业务方法。所以AQS队列中每一个节点线程都在自旋尝试获取锁,如果线程特别多,特别占用cpu资源。

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;
                }
                //shouldParkAfterFailedAcquire:获取锁资源失败后,应该停止这个线程。根据节点中线程的等待状态来判断,设置状态为Node.SIGNAL。
                //通过LockSupport.park阻塞线程。不让其自旋。再次执行时需要被唤醒。业务执行完毕,调用lock.unlock的时候会unpark头节点的后置节点线程。
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

 8. 返回到步骤3中的代码

步骤3中的代码如下:

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

1. tryAcquire(arg) 返回true时,则线程获取锁成功,执行线程业务。

2. tryAcquire(arg) 返回false时,则先执行addWaiter(Node.EXCLUSIVE)把当前线程封装为Node节点插入AQS队列,然后 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)尝试获取锁,返回true时,表明被中断。返回false时,表明尝试获取锁成功,执行线程业务。

ReentrantLock公平锁实现原理总结: 

 ReentrantLock的lock是AQS实现的,获取锁的过程主要分三步骤:

1. 判断AQS中的锁状态state和持有锁的线程exclusiveOwnerThread。

1.1 首先判断state是否为0,如果为0表明锁没有被其它线程持有,然后判断同步队列中头尾节点是否相等,如果相等,然后通过CAS修改线程状态,再设置AQS中exclusiveOwnerThread为当前线程。则获取锁成功。

1.2 如果判断 state!=0,再判断AQS中exclusiveOwnerThread持有锁的线程是否等于当前线程,如果相等,则判定为重入线程,则通过CAS修改线程状态。获取锁成功。

以上两步判断没通过,则获取锁失败,进入2步骤的判断。

2. 当前线程第一步获取锁失败后,封装当前线程为Node节点插入同步队列中。

2.1 调用方法addWaiter(Node.EXCLUSIVE)把当前线程封装为Node节点插入到AQS队列。插入过程中,先判断尾部节点是否为空,如果不为空,则通过CAS操作把当前线程节点插入到尾部节点,并且换旧尾部节点的后置节点和新尾部节点的前置节点。返回当前节点。

2.2 插入过程中,如果尾部节点为空,则会通过自旋的方式:首先再次判断尾部节点是否为空,为空创建一个新节点,插入到同步队列,如果不为空则重复2.1步骤插入AQS队列尾部。

3. 让AQS队列中的线程通过自旋的方式尝试获取锁。

自旋过程中判断当前节点前置节点的等待状态,如果等待状态为0(1:被取消,-1:等待被唤醒;-2:等待队列中的线程,需要被signal,-3:需要传播释放锁的状态需要被传播到的节点),则该线程会被标识为需要park,然后通过自旋再尝试获取锁,还没成功的话,就会被park,停止自旋,等待持有锁的线程释放锁时会调用unpark来唤醒同步队列中需要被唤醒的线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

荆茗Scaler

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值