JUC-AQS详解

1. AQS抽象队列同步器

AQS的全称是AbstractQueuedSynchronizer,翻译过来就是抽象队列同步器。这个类是在java.util.concurrent.locks包下面。
在这里插入图片描述

AQS就是一个抽象类,是用来构建锁和同步器的一个底层。

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

}

AQS底层构建锁和同步器提供了一个通用的功能的实现,因此使用AQS可以构建出应用广泛的同步器,比如我们常见的ReentrantLockCountDownLatchSynchronousQueue等等都是基于AQS的。

1.1 AQS的原理是什么?

AQS的核心思想是:如果被请求的共享资源空闲(state = 0),则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态(state: 0 -> 1)。如果被请求的共享资源被占用,那么就需要以掏线程阻塞等待以及唤醒时锁的分配机制,这个机制AQS是基于CLH队列锁实现的,即将暂时获取不到锁线程加入到队列中。

互斥:使用CAS将State变量从0改成1,并且只有一个线程会成功!
有一个地方会存放阻塞了的所有的线程
双向队列,CLH队列(Cfafasfas,Lfafsaf,Hfasfasf)

AQS是将每条请求共享资源的线程封装成一个节点(Node)来实现锁的分配。在CLH同步队列中,一个节点表示一个线程,它保存着线程的引用、当前线程在队列中的状态、前驱节点、后继节点。

static final class Node {
    volatile int waitStatus; //节点状态 ×
    volatile Node prev;          //前驱节点
    volatile Node next;          //后继节点
    volatile Thread thread;  //当前尝试加锁的线程
}

在这里插入图片描述
AQS(AbstractQueuedSynchronizer)的核心原理图。
在这里插入图片描述

AQS使用一个int成员变量state表示同步状态,通过内置的线程等待队列来完成资源线程的排队工作。

/**
 * The synchronization state.
 */
private volatile int state;

另外,状态信息state可以通过protected类型的getState()、setState()和compareAndSetState()进行操作。并且,这几个方法都是使用final修饰的,在子类中无法被重写。

protected final int getState() {
    return state;
}

protected final void setState(int newState) {
    state = newState;
}

//使用CAS保证线程安全
protected final boolean compareAndSetState(int expect, int update) {
    return STATE.compareAndSet(this, expect, update);
}

以ReentrantLock为例,state初始值为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占锁并将state + 1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state = 0为止,其他线程才会有机会来获取锁。当然,释放锁之前,A线程自己是可以重复获取锁的(state++),这就是可重入锁,但是要注意的是。获取多少次释放多少次,这样才会保证state回到0。

以CountDownLatch为例,任务分成N个子线程去执行,state直接初始化为N(N和线程个数一致)。每个线程countDown()一次,state会CAS(Compare And Swap)减1。等到所有子线程都执行完后(state = 0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后续动作。

2. ReentrantLock源码分析

ReentrantLock底层源码剖析
在这里插入图片描述

2.1 非公平锁加锁流程

static final class NonfairSync extends Sync {
    
    final void lock() {
        //ReentrantLock底层默认非公平锁
        //1.使用cas对state变量进行修改,state=0 => state=1
        if (compareAndSetState(0, 1))
            //2.如果修改成功,那么设置获取锁的线程为当前线程
            setExclusiveOwnerThread(Thread.currentThread());
        else
            //3.加锁失败,进入acquire(1)方法
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

如果不出现竞争的情况,直接cas就加锁成功。
当第一个竞争出现的时候就走acquire(1)方法:
在这里插入图片描述

Thread-1执行了:

  1. CAS尝试将state由0改成1,结果失败
  2. 进入tryAcquire逻辑,这时候state已经是1,结果仍然失败。
public final void acquire(int arg) {
    //tryAcquire再次尝试加锁,如果加锁不成功,就添加到阻塞队列
    if (!tryAcquire(arg) &&
        //addWaiter 创建一个node结点对象,并加入到等待队列
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  1. 接下来进入addWaiter逻辑,构造Node队列

    • 图中waitStatus表示该结点状态,其中0为默认正常状态。
    • 其中第一个结点Node是Dummy哨兵结点,用来占位,不关联线程
      在这里插入图片描述
  2. 当线程进入acquireQueued逻辑

    • acquireQueued会在一个死循环中不断尝试获取锁,失败后进入park阻塞。
    • 如果自己是紧邻着head(第二位),那么再次tryAcquire尝试获取锁,这时候state=1,获取锁失败。
    • 进入shouldParkAfterFailedAcquire逻辑,将前驱node,即head的waitStatus修改为-1,返回false。
      [图片]
  3. shouldParkAfterFailedAcquire执行完毕后,回到acquireQueued,再次tryAcquire尝试获得锁,当然这时候state=1,加锁失败。

  4. 当再次进入shouldParkAfterFailedAcquire时,这是因为前驱node的waitStatus已经是-1,这时候返回true

  5. 进入parkAndCheckInterrupt()调用LockSupport.park(this)阻塞加锁失败的线程。

//添加一个结点
private Node addWaiter(Node mode) {
    //1. 创建一个新节点
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;

    //2. 如果尾结点不为空,那么尝试直接入队(在下面的方法会再次入队)
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //3. 结点入队
    enq(node);
    return node;
}

//入队的方法
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // 4.初始化哨兵结点
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            //5. 将结点挂在虚拟节点后边(和步骤2一样)
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

//阻塞的方法acquireQueued
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //1. 获取当前结点的前驱节点
            final Node p = node.predecessor();
            //2. 如果发现当前节点是哨兵结点后的第一个结点,尝试加锁
            //(可能会竞争失败:因为是非公平锁)
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //3. shouldParkAfterFailedAcquire将node的前驱变成-1
            if (shouldParkAfterFailedAcquire(p, node) &&
                // 4.阻塞在这个地方!!!!
                parkAndCheckInterrupt()) 
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

//阻塞当前线程
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

2.2 解锁流程

解锁的时候调用unlock()即可。
在这里插入图片描述

public void unlock() {
    //解锁
    sync.release(1);
}

public final boolean release(int arg) {
    //if尝试解锁
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            //唤醒后继线程
            unparkSuccessor(h);
        return true;
    }
    return false;
}

//解锁流程 => 修改state和exclusiveThread持有锁的线程
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) {
    //将当前结点状态设置为0
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    //唤醒下一个阻塞的线程
    if (s != null)
        //线程在acquireQueued函数醒过来
        LockSupport.unpark(s.thread); 
}

2.3 ReentrantLock可重入原理

ReentrantLock在tryAcquire()获得锁的时候,判断当前线程和持有锁的线程是不是一个,如果是,那么就将state状态加1。

//尝试获取锁
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

//加锁
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;
}

//解锁
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;
}

2.4 ReentrantLock可打断原理

在线程调用LockSupport.park()阻塞的过程中,如果被打断,那么会直接throw一个异常,而不会继续尝试加锁。

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

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //尝试进行加锁
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }

            //可打断逻辑
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //在park过程中如果被interrupt,会进入这里
                //这时候直接抛出异常,而不会再次进入for(;;)循环
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

2.5 公平锁和非公平锁设计

公平锁和非公平锁的区别主要在于tryAcquire方法:

  1. 非公平锁每次tryAcquire(1)的时候,会直接判断state是不是为1,如果为1,那么直接调用cas尝试加锁。
  2. 公平锁每次加锁会先检查AQS队列中是否有前驱节点,没有才会去竞争锁。

公平锁加锁代码:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        //会检查AQS队列中是否有前驱节点,没有才会去竞争
        //or如果我是第一个结点,也会竞争
        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;
}

public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
    //h != t 表示队列中有Node
    return h != t &&
        (
        //(s = h.next) == null表示队列中还没有老二
        (s = h.next) == null || 
        //队列中老二线程是不是当前线程?
         s.thread != Thread.currentThread()
        );
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

陈卓410

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

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

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

打赏作者

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

抵扣说明:

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

余额充值