手撕源码系列之AQS -- AbstractQueuedSychronizer

简介

AQS是什么:
AQS是JDK中JUC中构建锁和其他同步装置的基本框架。其底层实现的数据结构是一个双向链表。

重点成员变量


    /**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     */
    private transient volatile Node head;

    /**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     */
    private transient volatile Node tail;

    /**
     * The synchronization state.
     */
    private volatile int state;

state

判断共享资源是否被占用的标志位(就是锁是否被占用)。
使用volatile修饰的int类型,保证了当前信息线程之间的可见性,
因为锁有两种状体
1. 独占: 一个线程占用的时候,其他线程无法占用
2. 共享: 一个线程占用的时候,其他线程也可以占用
所以state不用布尔类型,而是用int类型,可以提供有线程占用的数量

head 和 tail

head 和 tail 顾名思义是头节点和尾节点。
作用是线程在获取锁失败的情况下,可以进去缓存队列去等待获取锁。

Node对象

static final class Node {

        /** waitStatus 的四个等待状态 */
        static final int CANCELLED =  1;

        static final int SIGNAL    = -1;

        static final int CONDITION = -2;

        static final int PROPAGATE = -3;

        /** 等待状态 */
        volatile int waitStatus;

        /** 上一个Node对象 */
        volatile Node prev;

        /** 下一个Node对象 */
        volatile Node next;

        /** 线程对象 */
        volatile Thread thread;

        /**
         * Link to next node waiting on condition, or the special
         * value SHARED.  Because condition queues are accessed only
         * when holding in exclusive mode, we just need a simple
         * linked queue to hold nodes while they are waiting on
         * conditions. They are then transferred to the queue to
         * re-acquire. And because conditions can only be exclusive,
         * we save a field by using special value to indicate shared
         * mode.
         */
        Node nextWaiter;

重点方法

acquire

尝试获取锁立即返回

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

这里的acquired方法用的是publicfinal来修饰这个方法,说明就是让所有继承的子类直接来调用这个方法,且不允许重写。

方法内容:

  1. 尝试去获取锁且获取锁失败
  2. 进行排队等待锁(addWaiter操作是将当前线程放入到等待队列中,有点类似与LinkedHashMap的放入尾节点操作)

tryAcquire

获取锁,如果没有获取到,进入等待队列直到获取到锁

    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

当前方法就抛出了一个异常,就是让继承的子类去重写这个方法,否则就抛出不支持该操作的异常

acquireQueued

这个方法是当前线程会根据公平性原则来进行阻塞等待,直到获取锁为止(即排队等待锁);并且返回当前线程在等待过程中有没有中断过

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 {
        	//这一块只有在try代码块内抛出异常的时候,才会执行。目的是将node置为Cancel状态,并执行一些清理操作
            if (failed)
                cancelAcquire(node);
        }
    }

release 释放锁操作

因为部分节点在acquire方法的时候,无法获取锁且被挂起,而release方法在释放锁的时候,会唤醒挂起的线程去尝试获取锁。


    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

  
   public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

	private void unparkSuccessor(Node node) {
        
        int ws = node.waitStatus;
        //如果当前node内的等待状态不是Cancel则去重置waitStatus为0
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            //如果node的下一个node为null或者等待状态是Cancel则去为节点开始遍历获取node
           //为什么是从后往前便利获取是因为:因为插入的时候,cas和next节点赋值的时候可能会有其他线程打断,导致从前往后遍历会出现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);
    }

和acquire方法差不多,tryRelease方法明示让子类继承的时候去重写,否则就抛出异常。
release 方法则是释放锁的时候去唤醒挂起的线程,而线程被唤醒之后会在acquireQueued中继续执行获取锁的操作

问题

  1. 为什么unparkSuccessor唤醒线程的时候,当node的下一节点为null或者等待状态为Cancel的时候,是从尾结点开始往前遍历获取node对象
    答:因为插入操作的CAS操作和next节点赋值的时候可能会有其他系欸DNA打断,导致从前往后遍历的时候出现null。
    就是addWaiter方法中的
			if (compareAndSetTail(pred, node)) {
				//这一步操作
                pred.next = node;
                return node;
            }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值