AQS(AbstractQueuedSynchronizer)解析

   定义

    AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch。

数据结构

  它维护了一个state,代表共享资源,一个等待队列,多线程调用资源被阻塞时候就会进入此队列

   AQS使用了模板方法模式,自定义的同步器在在实现时只需要实现共享资源state的获取与释放即可,值域具体线程等待队列的维护,AQS已经在顶层实现好了。

模板方法主要有:

独占式获取

  • accquire
  • acquireInterruptibly
  • tryAcquireNanos

共享式获取

  • acquireShared
  • acquireSharedInterruptibly
  • tryAcquireSharedNanos

独占式释放锁

  • release

共享式释放锁

  • releaseShared

自定义同步器主要是实现以下方法

  • isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。

  • tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。

  • tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。

  • tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。

  • tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false

 

源码解析

先介绍一下它的变量及内部类Node节点。

    /** 队列中的头节点 */
    private transient volatile Node head;
    /** 队列中的尾节点 */
    private transient volatile Node tail;
    /** 同步状态、锁数量 */
    private volatile int state;

    /** 获取同步状态 */
    protected final int getState() {
        return state;
    }

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

    /** 通过CAS设置同步状态 */
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
   
static final class Node {
        static final Node SHARED = new Node();
        /** 表示在独占模式下等待 */
        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;

        /** node所属的线程 */
        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;
        }

    }

独占锁

    我重点介绍的是独占锁,因为共享锁用到的少。

  • 获取独占锁
获取独占锁流程
    // 获取锁
    public final void acquire(int arg) {
        // tryAcquire获取锁,如果state==0,或者持有锁的线程再次获取锁,那么就得到锁并修改state,可重入锁也是在这里提现出来的,由子类具体实现,
        // Node.EXCLUSIVE表示创建独占模式节点
        // 如果获取不到锁就将当前线程加入到队列
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

没有获取到锁,所以将节点加入到队列尾部

    // 将节点插入等待队列尾部
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // 如果尾节点不为空,则快速将node插入到队列尾部
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            // CAS设置当前节点为尾节点
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 否则,通过自旋方式直到成功
        enq(node);
        return node;
    }

    // 自旋将节点插入等待队列尾部
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            // 如果尾节点为空,说明队列中没有节点,那么就通过CAS初始化队列,并且头、尾节点指向同一个
            if (t == null) {
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                // 否则将本节点插入队列末尾
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

尝试去获取锁,如果获取不到,那么就进入休眠

    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; // 将之前的头结点的next设为null,表示从队列中脱离
                    failed = false;
                    return interrupted;
                }
                // 没获取到锁,那么就进入waiting状态,直到被unpark()
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                    // 线程被中断,那么就改变状态位
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }


    // 由方法名就可以看出是 获取锁失败后应该进入睡眠状态
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus; // 获取前驱节点的状态
        if (ws == Node.SIGNAL)
            // 如果状态为-1即下一节点需要被唤醒,那么返回true
            return true;
        if (ws > 0) {
            // 如果状态>0即该节点已经被废弃,那么往前遍历,直到找到正常等待的节点,并排在它后面
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            //如果前驱正常,那就把前驱的状态设置成SIGNAL,告诉它下一节点需要被唤醒
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

    // 线程进入睡眠
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);    // 进入睡眠,等待被唤醒
        return Thread.interrupted(); //如果被唤醒,查看自己是不是被中断的
    }

    /**
     * 如果线程发现线程被中断,那么就中断当前线程
     */
    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }
  • 超时等待获取锁

与获取锁逻辑一样,只不过多了时间判断

    public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }
	
	private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
		// 计算截止时间
        final long deadline = System.nanoTime() + nanosTimeout;
		// 将node添加到队列
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
				// 获取前驱节点,判断是否可以获取锁
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
				// 计算是否超时
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
					// 进入有超时时间的睡眠
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
  • 释放锁

释放锁逻辑比较简单,大致就是将state-1,唤醒下一个等待线程。

    public final boolean release(int arg) {
        // tryRelease()将state -1,如果state变为0,则将拥有锁的线程exclusiveOwnerThread置为null,由子类具体实现
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                // 如果头节点不为空并且状态不是0,那么就去唤醒下一个节点
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

唤醒下一个节点

    // 唤醒下一个没有被废弃的节点
    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        // 如果线程等待状态<0,那么就CAS变为0
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        // 如果下一个节点为空或者已经被废弃,则从尾部向前获取到状为<=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);
    }

 

共享锁

读写锁中的读锁,就是实现了共享锁。

  • 获取共享锁
    public final void acquireShared(int arg) {
        // tryAcquireShared尝试获取锁,返回小于0,则说明获取锁失败,由自类实现
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

tryAcquireShared()方法实现可以看 https://blog.csdn.net/zgsxhdzxl/article/details/106071399

没有获取到锁,则

    private void doAcquireShared(int arg) {
        // 将节点加入到尾部
        final Node node = addWaiter(Node.SHARED);
        boolean interrupted = false;
        try {
            for (;;) {
                final Node p = node.predecessor();
                // 前驱节点是头结点,则去获取锁
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    // 获取锁成功
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node))
                    interrupted |= parkAndCheckInterrupt();
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        } finally {
            if (interrupted)
                selfInterrupt();
        }
    }

获取锁成功之后,还会去唤醒后续的共享节点setHeadAndPropagate()

    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        // 将node设置为新的首节点
        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();
        }
    }

这里一直没搞明白,为什么会唤醒后续的共享节点,因为感觉循环只会唤醒一个节点。

    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                // 只有结点状态为SIGNAL才尝试唤醒后继结点
                if (ws == Node.SIGNAL) {
                    // 将waitStatus设置为0
                    if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
                        continue;    
                    // 唤醒后续的节点       
                    unparkSuccessor(h);
                }
                 // 如果状态为0则更新状态为PROPAGATE
                else if (ws == 0 &&
                         !h.compareAndSetWaitStatus(0, Node.PROPAGATE))
                    continue;               
            }
            if (h == head)                   
                break;
        }
    }

 

  • 释放锁
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

tryReleaseShared可以看 https://blog.csdn.net/zgsxhdzxl/article/details/106071399

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值