AQS(AbstractQueuedSynchronizer)源码解析

AQS是什么?
在 Lock 中,用到了一个同步队列 AQS,全称 AbstractQueuedSynchronizer,它是一个同步工具也是 Lock 用来实现线程同步的核心组件。
java.util.concurrent中大部分的工具都是通过AQS实现的。

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

在这里插入图片描述

如果看不明白不要怕继续往下看,看完回头再来看这个图。

接下来主要是通过ReentrantLock实现的Lock接口中的lock()锁来讲解上锁的过程。
AQS(AbstractQueuedSynchronizer)的类图如下:

类图

在这里插入图片描述

代码实例

代码示例:

public class AtomicDemo {
    private static int count=0;
    static Lock lock=new ReentrantLock();
    public static void inc(){
    //本文主要通过lock()方式来探究源码的实现过程
        lock.lock();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count++;
        System.out.println(count);
        lock.unlock();
    }
    public static void main(String[] args) throws
            InterruptedException {
        for(int i=0;i<1000;i++){
            new Thread(()->{AtomicDemo.inc();}).start();;
        }
        Thread.sleep(3000); System.out.println("result:"+count);
    }
}
ReentrantLock中的lock()方法
public void lock() {
        sync.lock();
    }
sync.lock()方法
abstract void lock();

sync.lock()有两个实现类,其中是一个是公平策略实现类,一个是非公平策略实现类。(公平策略与非公平策略后边会对比讲解)
ReentrantLock默认的是非公平方法实现类。可以根据ReentrantLock的构造方法来确定。

++++++++++++分割线(这是一段插曲)+++++++++++++++++

ReentrantLock构造方法代码
/**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

++++++++++++分割线结束+++++++++++++++++++++

回到sync.lock()方法的实现方法
在这里插入图片描述
在这里插入图片描述
lock()方法先通过CAS尝试将状态从0修改为1。若直接修改成功,前提条件自然是锁的状态为0,则直接将线程的OWNER修改为当前线程,这是一种理想情况,如果并发粒度设置适当也是一种乐观情况。

接下来分析这段代码了首先要进入的是compareAndSetState()方法主要的作用就是确定当前资源是否被占用,资源没有被占用就是理想状态。
代码的实现如下:
在这里插入图片描述

acquire(1)方法

如果非理想状态就会进入到acquire(1)方法中

public final void acquire(int arg) {
//对tryAcquire()的调用判定中是通过if(!tryAcquire())作为第1个条件的,如果返回true,则判定就不会成立了,自然后面的acquireQueued动作就不会再执行了,如果发生这样的情况是最理想的
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
接下来咱们去tryAcquire()中看看

在这里插入图片描述
ReentrantLock类中有多种tryAcquire()实现方案首先咱们进入公平策略的实现方法中进行分析

//公平策略
protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();//首先获取这个锁的状态
            if (c == 0) {//如果状态为0
                if (!hasQueuedPredecessors() &&//判断线程是否需要排队
                    compareAndSetState(0, acquires)) {//尝试设置状态为传入的参数(这里就是1)
                    setExclusiveOwnerThread(current);//若设置成功就代表自己获取到了锁
                    return true;
                }
            }
            //线程重入的体现,至于重入是什么可以先自行查找,此次不做说明
            else if (current == getExclusiveOwnerThread())//如果状态不是0,则判定当前线程是否为排它锁的Owner
             {
                int nextc = c + acquires;//如果是Owner则尝试将状态增加acquires(也就是增加1)
                if (nextc < 0)//如果这个状态值越界,则会抛出异常提示
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;//若没有越界,将状态设置进去后返回true(实现了类似于偏向的功能,可重入,但是无需进一步征用)
            }
            return false;//如果状态不是0,且自身不是owner,则返回false
        }
addWaiter()方法
    private Node addWaiter(Node mode) {
    //这里创建了一个Node的对象,将当前线程和传入的Node.EXCLUSIVE传入,也就是说Node节点理论上包含了这两项信息。代码中的tail是AQS的一个属性,刚开始的时候肯定是为null,也就是不会进入第一层if判定的区域,而直接会进入enq(node)的代码
        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;
    }
进入enq()方法
//看到了tail就应该猜到了AQS是链表吧,没错,而且它还应该有一个head引用来指向链表的头节点,AQS在初始化的时候head、tail都是null,在运行时来回移动。此时,我们最少至少知道AQS是一个基于状态(state)的链表管理方式。
    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;
                }
            }
        }
    }
接下来返回到acquireQueued()方法中
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();//这个方法返回node节点的前一个节点,也就是说只有当前一个节点是head的时候,进一步尝试通过tryAcquire(arg)来征用才有机会成功
                if (p == head && tryAcquire(arg)) {//p为node.predcessor()得到,这个方法返回node节点的前一个节点,也就是说只有当前一个节点是head的时候,进一步尝试通过tryAcquire(arg)来征用才有机会成功
                    setHead(node);//调用setHead(Node)的操作,这个操作内部会将传入的node节点作为AQS的head所指向的节点。线程属性设置为空(因为现在已经获取到锁,不再需要记录下这个节点所对应的线程了),再将这个节点的perv引用赋值为null。
                    p.next = null; // help GC//进一步将的前一个节点的next引用赋值为null。在进行了这样的修改后,队列的结构就变成了以下这种情况了,通过这样的方式,就可以让执行完的节点释放掉内存区域,而不是无限制增长队列,也就真正形成FIFO了
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) //这个方法内部会判定前一个节点的状态是否为:“Node.SIGNAL”,若是则返回true,若不是都会返回false,不过会再做一些操作:判定节点的状态是否大于0,若大于0则认为被“CANCELLED”掉了(我们没有说明几个状态的值,不过大于0的只可能被CANCELLED的状态),因此会从前一个节点开始逐步循环找到一个没有被“CANCELLED”节点,然后与这个节点的next、prev的引用相互指向;如果前一个节点的状态不是大于0的,则通过CAS尝试将状态修改为“Node.SIGNAL”,自然的如果下一轮循环的时候会返回值应该会返回true。&&
                    parkAndCheckInterrupt())//通过LockSupport.park(this)将当前线程挂起到WATING状态,它需要等待一个中断、unpark方法来唤醒它,通过这样一种FIFO的机制的等待,来实现了Lock的操作。
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

公平策略与非公平策略对比

在这里插入图片描述
公平策略与非公平策略的唯一区别就是判断是否需要排队。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值