AQS源码学习

AQS(全称AbstractQueuedSynchronizer抽象队列同步器)作为juc包下重要的基础类,是实现其他并发工具的基础类,来学习下AQS源码

1、类继承结构

先看看类继承结构,AQS继承了AbstractOwnableSynchronizer这个类
在这里插入图片描述

1.1、AbstractOwnableSynchronizer类的结构如下

public abstract class AbstractOwnableSynchronizer
    implements java.io.Serializable {

    private static final long serialVersionUID = 3737899427754241961L;

    protected AbstractOwnableSynchronizer() { }    
    
    // 独占模式同步下的当前拥有者线程
    private transient Thread exclusiveOwnerThread;
    
	// 设置独占模式下资源拥有者线程
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }
	// 获取独占模式下资源拥有着线程
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

2、AQS的模板方法

好了,AQS的父类就说到这里,继续来分析AQS。“同步器的设计是基于模板方法模式的,也就是说,使用者需要继承同步器并重写指定的 方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些 模板方法将会调用使用者重写的方法。”-引用自《java并发编程的艺术》 说白了要明白,AQS已经写好了一些方法(模板方法),这些是通用逻辑如:
在这里插入图片描述

2.1、 AQS使用者可能重写的方法

总共9个模板方法,本文只分析红框内的4个模板方法,其他方法分析与这4个类似。除此之外在使用AQS的时候,按照情况需要自行重写以下方法中的一些:
在这里插入图片描述
独占式表示同一时间同步资源只能被一个线程占有,这样在多线程场景下会降低效率;共享式表示同一时间同步资源可以被多个线程占有,这样多线程下会提升效率。注意到tryAcquireShared方法返回的数据大于等于0表示获取成功,大于0表示当前线程获取成功并且还有剩余资源,其他线程还可获取;等于0表示当前线程获取成功,但是没有剩余资源。在重写这个方法的时候要注意这个语义

3、AQS的field属性:

	static final class Node {}
	// 同步队列的头节点,头节点是获取了同步资源的节点
 	private transient volatile Node head;
	// 同步队列的尾节点
    private transient volatile Node tail;
 	// 同步资源
    private volatile int state;

3.1、Node节点是一个静态内部类:

	// 内部持有一个静态内部类Node,该类设计了等待线程的数据结构和状态
	static final class Node {
        // 共享模式的节点
        static final Node SHARED = new Node();
        // 标记表明是在独占模式下等待
        static final Node EXCLUSIVE = null;
        
        // waitStatus值,指示线程已取消
        static final int CANCELLED =  1;
        // waitStatus值,当前线程在释放同步状态时需要唤醒后继节点
        static final int SIGNAL    = -1;
        // waitStatus值,指示线程正在等待条件
        static final int CONDITION = -2;
        // waitStatus值,指示下一个acquireShared应该无条件传播
        static final int PROPAGATE = -3;

        // 等待状态
        volatile int waitStatus;
       	// 前驱
        volatile Node prev;
		// 后继       
        volatile Node next;
        // 等待节点的线程
        volatile Thread thread;
        
        // Node既可以作为同步队列节点使用,也可以作为Condition的等待队列节点使用。
        // 在作为同步队列节点时,nextWaiter可能有两个值:EXCLUSIVE、SHARED;
        // 在作为等待队列节点使用时,nextWaiter保存后继节点。
        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;
        }
    }

4、AQS模板方法分析

4.1、独占式获取锁acquire(int arg)源码

来分析独占式获取锁资源代码acquire()

	public final void acquire(int arg) {
		// 先调用tryAcquire获取资源,如果获取成功则返回,如果获取失败则构造等待队列的
		// 节点,即addWaiter(),然后调用acquireQueued()方法获取资源
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            // 获取过程中有终端,补上中断状态
            selfInterrupt();
    }  

4.1.1、addWaiter(Node mode)源码

	// 分析下addWaiter()方法
    private Node addWaiter(Node mode) {
    	// 这里以当前节点为等待线程构造一个新节点
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            // cas尝试进入队尾
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 进入队尾失败,进入enq,来看下这个方法
        enq(node);
        return node;
    }
4.1.1.1、enq(final Node node)源码
	private Node enq(final Node node) {
    	//(喔,又是死循环,李大爷写死循环有点东西的)在死循环里面,当前线程一直尝试
    	// cas排队尾,这就是经典的锁自旋
        for (;;) {
            Node t = tail;
            // tail为null,说明当前线程是第一个,所以要cas设置下队列头尾节点
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                // cas设置到队尾
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

4.1.2、acquireQueued(final Node node, int arg)源码

	// 好了,到这里addWaiter执行完毕,终于将当前线程放到了等待队列队尾,接下来看下acquireQueued这个方法
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            // cas尝试获取资源,如果获取成功就返回,否则可能要park,待当前线程被唤醒后
            // 继续cas自旋获取
            for (;;) {
            	// 获取到当前节点的前驱
                final Node p = node.predecessor();
                // 当前节点刚好是获取到资源的头节点并且当前线程也获取资源成功,则完成
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 当前节点的前驱不是头节点,或者获取资源失败。判断下是否要park
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
4.1.2.1、shouldParkAfterFailedAcquire(Node pred, Node node)源码
	// 看下shouldParkAfterFailedAcquire这个方法
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    	// 拿到前驱节点的状态
        int ws = pred.waitStatus;
        // 前驱节点已经设置好了,就可以放心去park了
        if (ws == Node.SIGNAL)
            return true;
        // 大于0表示当前节点的前驱节点放弃获取同步资源,需要循环往前面找没有放弃的
        // 最近的一个节点
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            // 尝试将前驱节点的状态设置为SIGNAL,无论成功与否,下次进来再观察
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        // 走到这里表示还不可以park
        return false;
    }	
4.1.2.2、parkAndCheckInterrupt()源码
	// 最后看下真正的park方法parkAndCheckInterrupt
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
    // 到此位置独占式获取同步资源的代码分析完了

独占式抢占锁流程:

  • 尝试先抢占一次,抢占成功返回
  • 抢占失败,当前线程包装成节点加入等待队列末尾,cas自旋加入队尾;
  • 加入队尾成功,尝试获取锁资源,获取成功,设置未头节点,返回;
  • 获取失败,cas判断是否需要休眠等待,只有当前节点的前驱节点状态是SIGNAL,才可以等待;
  • 前驱节点是SIGNAL,当前线程park下。

4.2、独占式释放锁release(int arg)源码

来分析独占式释放锁的源码release()

	public final boolean release(int arg) {
		// 如果释放成功
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
            	// 唤醒后继等待节点
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

unparkSuccessor(Node node)源码

	private void unparkSuccessor(Node node) {
        // 当前线程节点的状态
        int ws = node.waitStatus;
        // ws<0 是正常的节点状态
        if (ws < 0)
        	// cas设置节点状态,这里会设置成功,因为是独占式的
            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)
        	// 唤醒这个节点
            LockSupport.unpark(s.thread);
    }
    // 独占式释放锁的代码还是比较简单,就看到这里

4.3、共享式获取锁acquireShared(int arg)

再来分析共享式获取同步资源的代码

	public final void acquireShared(int arg) {
		// 这里尝试共享式获取同步资源,注意不同的是只要返回值>=0就表示获取成功
		// 因为tryAcquireShared由子类去具体实现,如果获取失败,我们重点看下
		// doAcquireShared这个方法
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

4.3.1、doAcquireShared(int arg)源码

	private void doAcquireShared(int arg) {
    	// 这里和独占式的addWaiter方法是一样的,都是cas插入队列的末尾,这里就不再赘述
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            // 这里进行自旋获取同步资源
            for (;;) {
            	// 获取节点的前驱
                final Node p = node.predecessor();
                // 前驱是头节点,那么就可以尝试获取同步资源
                if (p == head) {
                	// 尝试获取资源
                    int r = tryAcquireShared(arg);
                    // r>=0 获取同步资源成功了
                    if (r >= 0) {
                    	// 设置头并且将消息广播给后继节点,让后继节点也去获取资源
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                // 这里和独占式获取失败逻辑相同,不再分析
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
        	// 如果获取失败了,取消获取同步资源
            if (failed)
                cancelAcquire(node);
        }
    }
4.3.1.1、setHeadAndPropagate(Node node, int propagate)源码
	// 分析下setHeadAndPropagate这个方法
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        // 设置头
        setHead(node); 
        // 如果还有资源,广播给后继节点      
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
            	// 共享锁释放
                doReleaseShared();
        }
    }
4.3.1.1.1、doReleaseShared()源码
	// 看下doReleaseShared源码
   private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    // 唤醒后继节点
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }
    
	// 分析下unparkSuccessor源码
    private void unparkSuccessor(Node node) {
        // 节点状态
        int ws = node.waitStatus;
        if (ws < 0)
        	// 当前节点状态设置为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)
        	// 唤醒节点,让它也可以获取同步资源
            LockSupport.unpark(s.thread);
    }

4.4、共享式释放锁releaseShared(int arg)

分析下共享锁释放锁源码

	public final boolean releaseShared(int arg) {
		// 尝试释放资源,如果释放成功,则唤醒后续的节点
        if (tryReleaseShared(arg)) {
        	// 上面已经分析过了,简单来说就是唤醒后继的一个正常的节点
            doReleaseShared();
            return true;
        }
        return false;
    }

至此,AQS的独占式获取、释放资源;共享式获取、释放资源源码都分析完了,有了这些认识,后续在分析其他锁的时候就会简单许多。当然AQS还有其他一些重要方法没有分析到,后续有机会继续看下。

5、AQS中的Condition

Condition 中的node状态是CONDITION = -2,等待线程,在等待队列里面,当被唤醒时候,进入到
同步队列里面;在同步队列里面调用await,会进入等待队列。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值