AQS加锁流程(ReentrantLock)

定义

        全称是AbstractQueuedSynchronizer,AQS是JUC提供的一个用于构建锁和同步容器的基础类。JUC包内许多类都是基于AQS构建的,例如ReentrantLock、 Semaphore、CountDownIatch、ReentrantReadWriteLock、 FutureTask 等。通过内置的 CLH(FIFO)队列的变种来完成资源获取线程的排队⼯作,将每条将要去抢占资源的线程封装成⼀个 Node节点来实现锁的分配,有⼀个int类变量表示持有锁的状态,通过CAS完成对status值的修改。这种结构的特点是每个数据结构都有两个指针,分别指向直接的前驱节点和直接的后继节点。所以双向链表可以从任意-一个节点开始很方便地访问前驱节点和后继节点。每个节点其实是由线程封装的,当线程争抢锁失败后会封装成节点加入AQS队列中;当获取锁的线程释放锁以后,会从队列中唤醒一一个阻塞的节点

        本文以ReentrantLock作为具体实现来讲解AQS 的加锁流程,具体源码之前先看下AQS 和ReentrantLock的关系

 

在看具体实现源码之前咱们先看看关键的Node 的对象结构和AQS 队列的基本结构

 Node 结构

public class Node{ 
    volatile Node prev; //上一个节点 
    volatile Node next; //下一个节点 
    volatile Thread thread; //当前node包含的线程对象 
}

 AQS 同步队列结构

stats 表示重入次数,head 表示队头,tail 表示队尾。初始化状态时 status 为0 ,head 和tail 都为null,thread 为null。

 加锁流程

以下以ReentrantLock 的公平锁为例,非公平锁与之类似,有关公平锁和非公平锁参考我之前的文章。

第一种情况

第一个线程t0获取锁的时候没有其他线程, 代价基本为0cas改变锁的状态,继而记录当前持有锁的线程);队列的状态如下图

大致流程:

1 、获取当前线程
final Thread current = Thread.currentThread();
2 、获取锁的状态 getState();
int c = getState();
3 、判断锁的状态
if (c == 0)  此时c=0,接下来判断是否需要进行排队,而此时队列中没有线程,即队尾队头全是null,此时不需要进行入队操作,直接通过cas 加锁,加锁成功,修改当前持有的线程,更改重入次数。

核心源码
    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) {
                // 虽然是可以获取锁,但是依然还要判断队列里面是否有排队的县城
                // 因咱们的前提就是队列里面没有排队的线程,即可直接获取锁的状态,并进行相关操作
                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;
        }
    }

第二种情况

t0 没有释放当前锁,t1 此时来加锁,此时队列的初始状态为第一种情况的样子,此时t1会加加锁失败;返回false,然后调用addWaiter方法区初始化队列,然后自己入队;

1 、把 t1 封装成为一个 node Node node = new Node(Thread.currentThread(), mode);
2 、调用 ENQ 方法 enq(node);
3 、设置一个空的节点为尾部节点 if (compareAndSetHead(new Node()))
4 、让头部节点和尾部节点都指向这个 new 出来的空节点 tail = head;
5 、设置 t1-node 的上一个节点 =new node node.prev = t;
6 、设置队列的尾部为 t1-node if (compareAndSetTail(t, node))
7 、设置 new-node 的下一个节点为 t1-node t.next = node;
8 、入队成功之后调用 acquireQueued
9 acquireQueued 首先获取 t1-node 的上一个节点; final Node p = node.predecessor(); 获取上一个
节点
10 、判断上一个节点是否头节点
11 、如果是头结点则再次获取锁 ( 一次自旋 ) if (p == head && tryAcquire(arg)) {
12 、如果能够获取到锁表示锁被释放了
12-1 setHead(node);// 设置 t1-node( 当前加锁的线程 ) 为头节点
12-2 node.thread = null; //t1-node (当前 node )的 thread=null
12-3 node.prev = null;//t1-node (当前 node )的上一个节点 =null 12-4 p.next = null; // help GC new-node (上一个节点)的下一个节点指向 null ,为了 gc
13 t0 没有释放锁, t2 自旋之后还是没有获取到锁则 park 排队
此时队列的情况如下:

 核心源码

    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        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 (;;) {
                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) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

第三种情况

t2来加锁,t0依旧没有释放锁,t1此时在队列中, 与第二种情况类似,只是由于前一个节点不是head,不需要进行自旋,直接入队即可,最终队列的情况为

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值