AQS原理源码解读

原文指路

AQS类

1.类图

  • 红色线是内部类
    在这里插入图片描述
  • AbstractOwnableSynchronizer:抽象类,定义存储独占当前线程的属性和设置,获取当前线程的方法
  • AbstractQueuenSynchronize:抽象类,AQS框架核心类,内部以虚拟队列的方式管理线程的锁获取与锁释放,其中获取锁(tryAcquire方法)和释放锁(tryRelease方法)并没有提供默认的实现,需要子类重写方法的具体逻辑,目的是使开发人可以自定义获取锁和释放锁的方式
  • Node:AbstractQueuenSynchronize的内部类,用于构建虚拟队列(双向链表),为每个进入同步队列的线程封装成Node对象加入队列,管理需要获取锁的线程
  • Sync:抽象类,是ReentrantLock的内部类,继承AbstractQueuenSynchronize,实现了tryRelease方法,并提供抽象方法lock,供子类实现
  • NonfairSync:Reentrantlock的内部类,继承Sync,非公平锁的实现类
  • FairSync:Reentrantlock的内部类,继承Sync,公平锁的实现类
  • Reentrantlock:实现了Lock接口,创建时默认为非公平锁

2.略解

  • AQS采用的是模板方法模式,其内部除了提供并发的操作核心方法以及同步队列的操作之外,还提供了一些模板方法让子类自己实现,如加锁解锁
  • 原理部分详解

AQS底层结构

  • 基础部分请看上篇
    在这里插入图片描述

1.state

  • state=0:表示没有线程正在独占共享资源的锁
  • state=1:表示有线程正在共享资源的锁

2.CLH队列

  • Node内部结构
    在这里插入图片描述
  • 同步队列增删节点工作流程
    在这里插入图片描述
  • 等待队列增删节点工作流程
    在这里插入图片描述
    在这里插入图片描述

源码解读(acquire)

1.流程

在这里插入图片描述

2.源码

  • 入口:ReentrantLock的AQS的acquire()
public final void acquire(int arg) {
	//tryAcquire:尝试获取所,提供给子类实现
	//tryAcquire:返回false,!false为true表示线程需要进入同步队列,设置当前线程的状态为独占模式,也就是Node.EXCLUSIVE,进入addWaiter方法
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
------------------------------------------
private Node addWaiter(Node mode) {
	//将当前线程封装成Node节点
   Node node = new Node(Thread.currentThread(), mode);
   //tail默认为null,如果tail不为空,说明同步队列已经有节点
   Node pred = tail;
   if (pred != null) {
   		//将当前节点添加到尾节点
       node.prev = pred;
       //一次CAS设置尾节点
       if (compareAndSetTail(pred, node)) {
           pred.next = node;
           return node;
       }
   }
   //尾节点=null,当前节点是第一个节点,则进入enq方法
   //资源争抢太多,一次CAS失败,则进入enq方法
   enq(node);
   return node;
}
------------------------------------------
private Node enq(final Node node) {
	//通过自旋的方式
    for (;;) {
        Node t = tail;
        if (t == null) {如队尾为空,即同步队列为空
        	//首节点是虚拟节点
            if (compareAndSetHead(new Node()))//设置头节点,同时尾节点设置成头节点
                tail = head;
        } else {
        	//设置当前节点到尾部
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
  • 回退到acquire()上
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
    	//根据上面分析,addWaiter返回的是node,新插入节点,接着看acquireQueued方法
         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
------------------------------------------
final boolean acquireQueued(final Node node, int arg) {
	//标记是否成功拿到资源
    boolean failed = true;
    try {
    	//标记等待过程是否被中断
        boolean interrupted = false;
        //看到死循环就想到自旋
        for (;;) {
        	//获取node的前驱节点,注意head只是一个哨兵节点
            final Node p = node.predecessor();
            //前驱节点是头节点(哨兵) && 可是获取到锁
            if (p == head && tryAcquire(arg)) {
            	//则将当前线程节点设置为哨兵节点,头结点对应的thread属性为null,此时头节点已经获取到锁了
                setHead(node);
                //将之前的哨兵节点置成垃圾,方便垃圾回收gc
                p.next = null;
                //标识成功获取到资源
                failed = false;
                //表示线程未被中断,正常获取到锁
                return interrupted;
            }
            //若node的前驱不是头结点head,或是头节点但调用tryAcquire()尝试获取锁失败,则:
           	//1.shouldParkAfterFailedAcquire获取前驱节点状态,判断当前节点线程是否需要阻塞同时删除队列中取消的线程节点
            //      若不需要阻塞(前驱节点不处于唤醒状态)返回false,则再次进入自旋查找前驱并尝试获取锁
            //      若需要阻塞(前驱节点处于唤醒状态)则返回true,进入parkAndCheckInterrupt方法
            //2.parkAndCheckInterrupt阻塞当前线程在park位置,直到unpark或interrupt后再次检查该线程的中断状态,并返回中断状态
            //      若唤醒后该线程不处于中断状态返回false,则再次进入自旋查找前驱并尝试获取锁
            //      若唤醒后该线程处于中断状态返回true,则设置interrupted=true,则再次进入自旋查找前驱并尝试获取锁,但返回会是true
            //          回到acquire()中执行selfInterrupt()方法,完成当前线程的中断信号发出
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                interrupted = true;
    } finally {
    	if (failed)//未成功获取到资源,且此时
	        //如果node的前驱结点为空,说明node为头结点,代码抛出异常
	        cancelAcquire(node);//取消对资源的获取
    }
}
------------------------------------------
//cancled表示node所代表的线程已取消排队,需要从同步队列移除
//signal意思是前驱节点释放锁,则当前节点被唤醒,其后继节点需要被阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //根据前驱结点的状态判断是否将线程阻塞
    int ws = pred.waitStatus;//前驱节点
    if (ws == Node.SIGNAL)//前驱节点处于唤醒状态,只要前驱的前驱释放锁,前驱节点就运行,当前线程阻塞
        return true;
    if (ws > 0) {//前驱节点处于cancled状态(原因可能是超时或中断),则将所有处于取消状态的前驱节点从同步队列中删除
        //让当前节点的前驱节点不断循环,跳过所有被撤销的前驱节点,指向一个最近ws<0(未撤销的)的前驱节点
        do {
            //此时前驱节点是取消状态,修改前驱指向前驱的前驱
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        //退出循环表示当前pred就是Node结点的前驱结点,此时已经将同步队列中所有取消的节点移除
        pred.next = node;
    } else {//前驱未取消,通过CAS设置前驱节点状态为唤醒
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    //返回false表示不阻塞线程
    return false;
}
------------------------------------------
private final boolean parkAndCheckInterrupt() {
	//阻塞当前线程,一直阻塞到当前位置,直到被唤醒(unpark或interrupt)
    LockSupport.park(this);
    //直到被唤醒后,重新判断当前线程中断状态
    //从unparkSuccessor()中被unpark后判断线程是否被中断,被中断返回true,interrupted被设置为true作为acquireQueued()方法的返回进入到下面的selfInterrupt通知线程中断
    return Thread.interrupted();
}
  • 再次回退到acquire()上
public final void acquire(int arg) {
   if (!tryAcquire(arg) &&
       acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
       //返回false,则表示已进入队列,或者获取到资源
       //返回true,则进入selfInterrupt方法,方法里面很简单
       selfInterrupt();
}
------------------------------------------
static void selfInterrupt() {
	//中断线程
	Thread.currentThread().interrupt();
}

源码解读(release)

public final boolean release(int arg) {
    if (tryRelease(arg)) {//释放资源成功,进入if
        Node h = head;//获取同步队列头结点,就是当前需要释放的节点
        //如头结点非空,且头结点状态不为0,那么队列中可能存在待唤醒的结点
        if (h != null && h.waitStatus != 0)
            //唤醒后继结点,让后继结点竞争资源(parkAndCheckInterrupt()中的park)
            unparkSuccessor(h);
        //释放独占锁成功,返回true
        return true;
    }
    //释放独占资源失败,返回false
    return false;
}
-------------------------------------
private void unparkSuccessor(Node node) {
   	int ws = node.waitStatus;
    if (ws < 0)//结点状态小于0,则肯定不是cancled态
        //CAS将结点状态设为0
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;//取下一个节点
    //如后续为空 或 是取消状态,说明后继结点对应的线程取消对资源的等待
    if (s == null || s.waitStatus > 0) {
        s = null;
        //从同步队列队尾结点开始向前遍历,找到node结点第一个不是取消状态的结点,如遍历到的结点t非空且不等于当前node,则校验此结点t的状态
        //简单来说就是寻找到下一个非取消态的结点s
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }

    if (s != null)
        //唤醒后继节点对应的线程 ---> 唤醒后从parkAndCheckInterrupt()的park处醒来判断中断状态
        LockSupport.unpark(s.thread);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值