并发源码解析(一)--抽象同步队列AQS

本文深入解析了AbstractQueuedSynchronizer(AQS),它是Java并发包中锁和同步器的基础组件。文章详细介绍了AQS的加锁流程、源码分析,包括继承与实现、属性、获取与释放资源等关键部分,以及线程同步的关键操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、AQS

AQS,即AbstractQueuedSynchronizer抽象同步队列,是实现同步器的基础组件,并发包中锁的底层就是使用AQS实现的,因此这个类非常重要。

因此在谈到并发包的源码时,先讲一下AQS

简单的先做下AQS的加锁流程

在这里插入图片描述

二、源码分析

2.1 继承与实现

在这里插入图片描述
在这里插入图片描述
可以看到AQS继承了AbstractOwnableSynchronizer。由线程独占的同步器,这个类为创建锁和相关同步器提供了基础,这个类比较简单。

public abstract class AbstractOwnableSynchronizer
    implements java.io.Serializable {

    /** Use serial ID even though all fields transient. */
    private static final long serialVersionUID = 3737899427754241961L;

    /**
     * Empty constructor for use by subclasses.
     */
    protected AbstractOwnableSynchronizer() { }

    /**
     * 互斥模式下的当前线程
     */
    private transient Thread exclusiveOwnerThread;

    /**
     * 设置当前独占访问的线程,是拥有锁的线程
     */
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

    /**
     * 返回设置的线程
     */
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

2.2 属性

AQS是一个FIFO的双向队列,内部通过节点head和tail纪录队首和队尾元素,队列元素的类型为Node。

先看一下Node节点

   static final class Node {
        /** 标记该线程是获取共享资源时被阻塞挂起后放入AQS队列的 */
        static final Node SHARED = new Node();
        /** 标记线程是获取共享资源时被阻塞挂起来放入AQS队列的 */
        static final Node EXCLUSIVE = null;

        /** 表示当前线程被取消了 */
        static final int CANCELLED =  1;
        /**后续节点处于等待状态,而当前节点的线程如果释放了同步状态或被取消,将会通知后继节点,使后继节点的线程得以运行 */
        static final int SIGNAL    = -1;
        /** 线程在条件队列里面等待 */
        static final int CONDITION = -2;
        /**
         * 释放共享资源时需要通知其他节点
         */
        static final int PROPAGATE = -3;

        /**
         * 纪录当前线程的等待状态
         */
        volatile int waitStatus;

        /**
         * 当前节点的前驱节点
         */
        volatile Node prev;

        /**
         * 当前节点的后继节点
         */
        volatile Node next;

        /**
         * 当前节点的线程
         */
        volatile Thread thread;

        /**
         * 条件线程的后继节点
         */
        Node nextWaiter;

        /**
         * 判断节点状态是否是共享模式
         */
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        /**
         * 返回当前节点的前驱节点
         */
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    // Used to establish initial head or SHARED marker
        }

		//带节点模式和线程的构造器
        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }
		//条件队列使用
        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

AQS的主要属性:

//AQS队列的头结点
private transient volatile Node head;

/**
 * AQS队列的尾节点
 */
private transient volatile Node tail;

/**
 * 同步状态
 */
private volatile long state;

/**
 * 返回当前的同步状态
 */
protected final long getState() {
    return state;
}

/**
 * 设置当前的同步状态
 */
protected final void setState(long newState) {
    state = newState;
}

/**
 *CAS方式设置同步状态
 */
protected final boolean compareAndSetState(long expect, long update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapLong(this, stateOffset, expect, update);
}

可以看到在AQS中维持了一个唯一的状态信息state,可以通过方法来修改其值。对于ReentrantLock的实现来说,state可以用来表示当前线程获取锁的可重入次数;对于Semaphore来说,state用来表示当前可用信号的个数;对于CountDownlatch来说,state用来表示计数器当前的值

对AQS来说,线程同步的关键是对状态值state进行操作。根据state是否属于一个线程,操作state的方式分为独占方式和共享方式。

独占方式:只有一个线程能执行,如ReentrantLock
共享方法:多个线程可以同时执行,如Semaphore和CountDownlatch

2.3 获取与释放资源–独占方式

public final void acquire(long arg) {
	//很短但是有很多方法,详细看下面的
	//tryAcquire成功,不调用此方法,失败则调用acquireQueued方法加入队列
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

	//独占模式下,尝试去获取锁(子类需要重写此方法)
    protected boolean tryAcquire(long arg) {
        throw new UnsupportedOperationException();
    }

	//获取失败,将当前线程封装为Node.EXCLUSIVE的node节点并添加至AQS阻塞队列尾部
    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;
        //如果尾节点不为null
        if (pred != null) {
        	//新节点的前驱指向尾节点
            node.prev = pred;
            //设置当前节点为尾节点,CAS,确保入队是原子操作
            if (compareAndSetTail(pred, node)) {
            	//将旧的尾节点的后续指向新节点
                pred.next = node;
                //返回新加入的node
                return node;
            }
        }
        //阻塞队列还未初始化,节点入队
        enq(node);
        return node;
    }
    
	//节点入队操作
    private Node enq(final Node node) {
    	//进入死循环
        for (;;) {
        	//获取阻塞队列的尾节点
            Node t = tail;
            //尾节点为null,还未初始化,进入if语句
            if (t == null) { // Must initialize
            	//初始化阻塞队列,将新的节点设置为头节点
                if (compareAndSetHead(new Node()))
                	//尾节点指向头节点(队列中此时只有一个元素)
                    tail = head;
                    //初始化后进行第二次for循环,进入else语句
            } else {
            	//尾节点设置为传入新节点的前驱
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                	//将旧的尾节点后继设置为传入的节点
                    t.next = node;
                    //退出for循环,唯一出口
                    return t;
                }
            }
        }
    }

	//当前线程在死循环中尝试获取同步状态
    final boolean acquireQueued(final Node node, long arg) {
    	//失败标志
        boolean failed = true;
        try {
        	//打断标志
            boolean interrupted = false;
            for (;;) {
            	//获得当前节点的前驱节点的引用p
                final Node p = node.predecessor();
                //如果p是阻塞队列的头部,就尝试获取锁
                if (p == head && tryAcquire(arg)) {
                	//获取到,将node设置为head节点
                    setHead(node);
                    //将原头部的next设置为null,help GC
                    p.next = null; // help GC
                    //加锁成功设置标记为false
                    failed = false;
                    //返回打断标志
                    return interrupted;
                }
               //如果满足条件就挂起当前线程&&获取是否中断
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
        	//如果失败
            if (failed)
                cancelAcquire(node);
        }
    }

	//判断是否可以将节点挂起
   private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
   		//前驱节点的等待状态
        int ws = pred.waitStatus;
        //如果是SGINAL,代表前驱节点完成后会唤醒后继节点,当前节点就可以挂起了
        if (ws == Node.SIGNAL)
            return true;
        //大于0是CANCEL状态
        if (ws > 0) {
            /*
             * 清除已经被取消的前驱节点
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * CAS操作设置前驱节点状态为SIGNAL,后面会返回false,然后重复判断
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
	
	//挂起时检查中断标记
    private final boolean parkAndCheckInterrupt() {
    	//挂起当前线程
        LockSupport.park(this);
        //返回中断标记,如果是正常唤醒则返回false,中断醒来则返回true
        return Thread.interrupted();
    }

	//进入这里代表抛出异常,进行异常处理
   private void cancelAcquire(Node node) {
        //节点不存在则忽略
        if (node == null)
            return;

		//节点线程设置为null
        node.thread = null;

        // 跳过已取消的节点,同上面CANCENL状态操作一样
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        // predNext is the apparent node to unsplice. CASes below will
        // fail if not, in which case, we lost race vs another cancel
        // or signal, so no further action is necessary.
        Node predNext = pred.next;

        // 把当前节点的waitStatus状态设置为取消
        node.waitStatus = Node.CANCELLED;

        //如果是尾节点,直接CAS尾节点
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            // If successor needs signal, try to set pred's next-link
            // so it will get one. Otherwise wake it up to propagate.
            int ws;
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                	//如果当前节点的前驱节点不是头节点且后面的节点等待他唤醒
                	//将前驱节点与后继节点相连
                    compareAndSetNext(pred, predNext, next);
            } else {
            	//当前节点的前置是头结点,直接唤醒后继节点
                unparkSuccessor(node);
            }

            node.next = node; // help GC
        }
    }

	//醒来后判断是否被打断
	//是则将自己中断
    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

2.4 线程释放锁

//释放锁的操作
public final boolean release(long arg) {
	//tryRelease需要子类自己实现
    if (tryRelease(arg)) {
        Node h = head;
        //判断有无需要被唤醒的节点
        if (h != null && h.waitStatus != 0)
        	//唤醒后继节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}

    private void unparkSuccessor(Node node) {
        /*
         * 判断表示是否为小于0,把标记设为0
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * 如果后继节点为null或被取消
         */
        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)
            LockSupport.unpark(s.thread);
    }

基本上就这些了,比较复杂,要慢慢来

三、总结

AQS在获取锁的同时,还会维护一个FIFO队列。获取锁失败的线程会加入队列,并在队列中进行自旋。自旋的同时会判断前驱节点的状态,然后使用LockSupport.park方法进行阻塞。当前驱节点是头节点且成功获取了同步状态时,线程移除队列。释放同步状态的同时,根据节点状态判断后继节点是否需要唤醒,然后唤醒头节点的后继节点

自己做的流程图,也不知道对不对,有错及时分享下

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值