2_1AQS源码分析

AQS源码分析

AQS是Concurrent包下的核心类,很多的类包括ReentrantLock, CountDownLatch, CyclicBarrier, ReadWriteLock等包都是通过AQS这个类实现的。

这篇文章将会和大家一起看一下AQS的源码分析,从而了解AQS的内部运行情况。

AQS的核心内容包括三大块(先了解一下):

​ state状态:0表示锁处于释放状态,1-n表示有线程取得锁,可以为n表示可重入锁;

​ queue双向链表:没有拿到锁的线程进入queue中等待;

​ CAS保证原子操作:因为可能多个线程同时去争抢锁,也就是修改state的值,还有多个线程加入queue队列,所以,通过CAS保证原子性(它的高效性体现在如果不使用CAS,而是对整个queue加synchronized锁,效率会很低)。

先看一下总体流程图,看不懂没关系,下面一步步分析;

流程简介:
当一个线程通过reentrantlock.lock()获取锁的时候,

会先尝试CAS修改state的状态从而获取锁,成功,直接修改state的状态;

如果失败,再次尝试修改状态,此次允许可重入;

如果失败,进入等待队列,然后自旋等待状态被释放;

这个过程中,只有队列的第二个节点会进行tryAcquire尝试再次获取,

其他节点,尝试阻塞;通过lockSupport.lock();

AQS的实现的锁的种类:

  1. 公平和非公平:一个线程通过lock()进来的时候是否直接.acquire()抢锁还是先去队列判断有没有等待的线程;

  2. 可重入:state只有0/1 还是可重入

  3. 共享和独占:共享锁相比与独占锁,可以多个线程同时拿到锁,原理:propogate,每一个线程拿到锁后,循环让下一个线程来拿锁;会先判断是否被独占。 没有详细看,只是看了一下流程?

  4. ReentrantReadWriteLock, 基于共享锁的实现;

  5. Semaphore, 可以控制并发量。基于共享锁的实现;每次.await()将Semaphore-1; 当semaphor信号量减到0的时候,所有的线程propogate获取锁;

  6. CountDownLatch 其他线程.countdown(); latch–;主线程.await(), 当latch为0的时候,其他线程.await()释放

    也是共享模式,多个线程可以CAS操作state–; 当state为0的时候,所有.await()线程开启;

  7. CyclicBarrier .await(). state–。 state为0后,所有的线程开启。

下面,我们以ReentrantLock加锁解锁的过程为例来分析这个过程。

1.这是一个demo

我们获得lock锁,然后对其lock加锁和unlock解锁。接下来看一下lock的源码;
在这里插入图片描述

2.ReentrantLock调用Sync类中的lock方法。

在这里插入图片描述

Sync什么类呢?它是ReenntrantLock的内部类,

在这里插入图片描述

通过看它的继承树可以直到,它是AQS的子类。 NonSync–> Sync --> AQS
在这里插入图片描述

3. 再来看sycn中的lock方法

在这里插入图片描述

首先这里调用了CAS去抢锁。如果成功了,就设置为exclusive独占锁。如果失败了,调用acquire(1)方法。

  1. 再来看一下acquire方法。

!](https://img-blog.csdnimg.cn/20200504183845892.png)

acquire方法,分成了3步,

第一步:再次尝试获得锁,获取成功,就获得锁,而且这里会进行重入锁的判断,看4.1

第二步:addWaiter,通过CAS操作将当前Thread构建node加到队列尾中;看4.2

第三步:acquireQueued,再队列中不断尝试获取锁,或者挂断;看4.3

4.三步

4.1 tryAcquire调用的是nonfairTryAcquire(), 默认的是非公平锁,所以这里,直接放这个方法。在这里会进行可重入锁的判断。

在这里插入图片描述

4.2 如果没有获得锁,加入队列。这里的2.1 CAS加入队列会尝试加锁,如果失败后,if之前的node.pre = pred还是执行,可能会出现多个node的前node指向了同一个node。 因此,失败后,在enq方法中再次尝试CAS操作。

在这里插入图片描述

在这里插入图片描述

4.3 加锁队列后,acquireQueue会循环尝试获得锁。

这里分成两块,

如果当前node的前置节点是个head,也就是正在拿到锁的运行线程,那么它可能即将获得锁,因此,tryAcquire尝试获得锁,如果还是失败,在进入3.3 挂起的条件,如果满足,就挂起,等待前置节点完成后,唤醒它。

第二块,非第二个node,也就是当前节点的前置节点不是head,那么它可能等待的时间较长,会直接进入3.3尝试挂起.等待前置节点完成后,唤醒它。

接下来,看一下unlock。释放锁的代码

在这里插入图片描述

5. 释放锁

在这里插入图片描述

6. tryRelease

在这里插入图片描述

7. unPark

在这里插入图片描述

8. 公平锁和非公平锁

public ReentrantLock() {
    // 默认非公平锁
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
 
static final class FairSync extends Sync {
    final void lock() {
        acquire(1);
    }
    // AbstractQueuedSynchronizer.acquire(int arg)
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 1. 和非公平锁相比,这里多了一个判断:是否有线程在等待
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                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;
    }
}
 
 
static final class NonfairSync extends Sync {
    final void lock() {
        // 2. 和公平锁相比,这里会直接先进行一次CAS,成功就返回了
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    // AbstractQueuedSynchronizer.acquire(int arg)
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}
/**
 * Performs non-fair tryLock.  tryAcquire is implemented in
 * subclasses, but both need nonfair try for trylock method.
 */
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 这里没有对阻塞队列进行判断
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    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;
}

总结:公平锁和非公平锁只有两处不同:

  1. 非公平锁在调用 lock 后,会调用 CAS 进行一次抢锁
  2. 在 tryAcquire 方法中,如果(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,

相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。

总结

在并发环境下,加锁和解锁需要以下三个部件的协调:

锁状态。 state 的作用,它为 0 的时候代表没有线程占有锁,可以去争抢这个锁,用 CAS 将 state 设为 1,如果 CAS 成功,说明抢到了锁,这样其他线程就抢不到了,如果锁重入的话,state进行 +1 就可以,解锁就是减 1,直到 state 又变为 0,代表释放锁,所以 lock() 和 unlock() 必须要配对啊。然后唤醒等待队列中的第一个线程,让其来占有锁。
线程的阻塞和解除阻塞。AQS 中采用了 LockSupport.park(thread) 来挂起线程,用 unpark 来唤醒线程。
阻塞队列。

争抢这个锁,用 CAS 将 state 设为 1,如果 CAS 成功,说明抢到了锁,这样其他线程就抢不到了,如果锁重入的话,state进行 +1 就可以,解锁就是减 1,直到 state 又变为 0,代表释放锁,所以 lock() 和 unlock() 必须要配对啊。然后唤醒等待队列中的第一个线程,让其来占有锁。
线程的阻塞和解除阻塞。AQS 中采用了 LockSupport.park(thread) 来挂起线程,用 unpark 来唤醒线程。
阻塞队列。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值