细品AbstractQueuedSynchronizer底层源码 (一)

AbstractQueuedSynchronizer的基本介绍

前言

关于AbstractQueuedSynchronizer以下简称AQS是一个线程安全的同步框架,它是lock接口实现的核心思想,大家所了解到的ReentrantLock,线程池等都依赖于lock,追其根源还是基于AQS的实现。

正文

原理概览

AQS核心思想是,如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中。

CLH:Craig、Landin and Hagersten队列,是单向链表,AQS中的队列是CLH变体的虚拟双向队列(FIFO),AQS是通过将每条请求共享资源的线程封装成一个节点来实现锁的分配。

通俗的来讲就三点

  • state状态
  • 队列
  • cas
    状态意味着锁资源,而队列里存储的节点就相当于没有获取到锁阻塞的线程,cas来确保锁释放后,由另一个斜体样式

常量概览

 /**
     * 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.
     */
      // 同步状态,对其修改靠CAS
    private volatile int state;
    
  /**
     * The number of nanoseconds for which it is faster to spin
     * rather than to use timed park. A rough estimate suffices
     * to improve responsiveness with very short timeouts.
     */
     // 等待时间的阈值,线程获取锁失败的情况下可能会转变成阻塞状态,转变成阻塞状
     // 如果正在占用锁的线程释放锁,又得将线阻塞线程进行唤醒
     // 所以如果线程在极短的时间释放锁,我们让等待线程不去阻塞,等待它释放后
     // 立即占用,所以这个等待阈值长就是spinForTimeoutThreshold
     //这样可以节省资源,操作系统的用户态和内核态的切换都是消耗性能的
    static final long spinForTimeoutThreshold = 1000L;

  /**
     * Setup to support compareAndSet. We need to natively implement
     * this here: For the sake of permitting future enhancements, we
     * cannot explicitly subclass AtomicInteger, which would be
     * efficient and useful otherwise. So, as the lesser of evils, we
     * natively implement using hotspot intrinsics API. And while we
     * are at it, we do the same for other CASable fields (which could
     * otherwise be done with atomic field updaters).
     */
     // unsafe 的调用,获取各个字段的偏移量
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long stateOffset;
    private static final long headOffset;
    private static final long tailOffset;
    private static final long waitStatusOffset;
    private static final long nextOffset;

    static {
        try {
            stateOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
            headOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
            tailOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
            waitStatusOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("waitStatus"));
            nextOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("next"));

        } catch (Exception ex) { throw new Error(ex); }
    }

队列的头尾节点定义都很直白,stateOffset这种字段偏移量的获取要依赖unsafe的调用,所以静态代码块里就对unsafe进行反射方式的调用获取个个字段的偏移量,关于unsafe,读者都知道java的jvm虚拟机并不是由java编写而是c++编写的,unsafe这个类就是调用本地的c++方法,既然以unsafe命名了,自然是存在安全问题的,所以java不让直接调用,通过反射方式来调用。AQS的CAS(compareAndSet)是调用unsafe来实现的。

讲到cas就简单提及提及一下它的思想:CAS(compare and swap) 比较并替换,就是将内存值更新为需要的值,但是有个条件,内存值必须与期望值相同。举个例子,期望值 E、内存值M、更新值U,当E == M的时候将M更新为U。
volatile这个关键字修饰后的变量表示对所有线程可见。

接下来观察一下其内部类Node

 static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        // 标明共享模式等待锁
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        // 标明独占模式等待锁
        static final Node EXCLUSIVE = null;

        /** waitStatus value to indicate thread has cancelled */
        // 标明等待线程取消了
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */
        // 标明线程准备就绪,等待正在使用锁的线程释放资源
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        // 标明这个线程在condition队列中进行等待,
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
         // 共享模式下才会用到
        static final int PROPAGATE = -3;
        // 等待状态
        // 上面提到的值都是状态的值
        // 状态还有个默认值为0
        volatile int waitStatus;
        // 前驱节点
        volatile Node prev;
        // 后续节点
        volatile Node next;
        // 当前节点的线程
        volatile Thread thread;	
        // 指向下一个condition队列waiting状态的节点,或者标明节点是否共享还是独占
        Node nextWaiter;
        // 通过nextWaiter判断共享还是独占
 final boolean isShared() {
            return nextWaiter == SHARED;
        }

方法概览

大概看一下AQS的一下模板方法

	// 独占式获取锁
    public final void acquire(int arg) 
    // 独占式获取锁,可响应中断
    public final void acquireInterruptibly(int arg)
     // 独占式获取锁,可响应中断,超时返回
    public final boolean tryAcquireNanos(int arg, long nanosTimeout)
     // 释放独占锁
     public final boolean release(int arg) 
     // 共享式获取锁
     public final void acquireShared(int arg)
      // 共享式获取锁,可响应中断
     public final void acquireSharedInterruptibly(int arg)
      // 共享式获取锁,可响应中断,超时返回
     public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
     //  释放共享锁
     public final boolean releaseShared(int arg)

具体观察下acquire方法

 public final void acquire(int arg) {
 		// 获取锁失败并且阻塞队列为空,就执行中断
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    // tryAcquire由子类去覆写
      protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
       /**
     * Creates and enqueues node for current thread and given mode.
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
     // addWaiter方法根据当前前程创建一个入队的节点
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        // 队列尾插法,队尾入,队头出
        // 队列不为null的话,就cas插入一次,失败的话就enq方法自旋插入
        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;
            // 尾节点为null,相当于队列为null,进行初始化
            if (t == null) { // Must initialize
            	// 新建节点cas操作后赋给头节点,此时队列就一个节点
            	// 既是头结点也是尾节点
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            	// 队列不为null,将新增的节点作为尾节点插入
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

可能有读者不清楚自旋的概念,自旋有点类似死循环,但与死循环的差异在与死循环是会无限循环下去,自己无法终止循环,而自旋是可以自身终止循环的,enq方法中自旋的终止条件就是return t;
接下来看下acquireQueued方法

 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            	// 获取node的前驱节点p
                final Node p = node.predecessor();
                // p是头节点并且获取锁成功。
                if (p == head && tryAcquire(arg)) {
                	// node设置为头节点并将头节点赋值为空节点
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 获取锁失败后判断是阻塞还是自旋等待以及进行阻塞和中断检查
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    // 通过lockSuport类的调用来进行阻塞
                    // 设置中断值为true,但并不代表线程已中断
                    interrupted = true;
            }
        } finally {
        	// 失败,取消线程
            if (failed)
                cancelAcquire(node);
        }
    }
  /**
     * Checks and updates status for a node that failed to acquire.
     * Returns true if thread should block. This is the main signal
     * control in all acquire loops.  Requires that pred == node.prev.
     *
     * @param pred node's predecessor holding status
     * @param node the node
     * @return {@code true} if thread should block
     */
     // 检查获取锁失败的线程是否该阻塞,true即阻塞处理
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        // node节点的前驱节点等待状态
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
             // signal状态可以安心阻塞,如果被释放获取取消,能够通知它的后续节点
            return true;
            // ws > 0那说明ws的值为CANCELLED
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
            // 取消掉之后,就将这个节点从队列删除并进行调整,即node的前驱为被移除的
            // 的节点的前驱节点。
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
             // 剩下的值要不为0初始化状态要不为PROPAGATE(共享锁状态下才会用到这个值)
             // 这时候尝试cas置换一下等待状态为SIGNAL,也就是说不进行阻塞再尝试获取下锁
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

解释一下为什么要寻找前驱为signal状态,signal状态之前笔提到过,相当于一个标志,但这个标志并不是用于自己,而是用于下一个节点,当这个节点的线程释放到锁后,就会唤醒下个线程去获取锁。比如说周末下馆子,馆子人很多你领个号要排队很久,于是你找了你号码牌前一位的哥们,拜托轮到他的时候告知你一下(相当于给前驱打上signal状态),你就可以安心的小睡一会儿(等同于安全的park挂起)。
继续看下cancelAcquire 方法

private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
        // 非空判断
        if (node == null)
            return;

		// 清除节点上的线程,节点和任何线程都没有引用关系
        node.thread = null;

        // Skip cancelled predecessors
        // 跳过cancelled的前继节点,找到一个有效的前继节点 
        Node pred = node.prev;
        // >0 说明为cancelled状态
        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;

        // Can use unconditional write instead of CAS here.
        // After this atomic step, other Nodes can skip past us.
        // Before, we are free of interference from other threads.
        // 注释解释了一下为何直接赋值而不是通过CAS安全的方式来赋值
        // node的线程已经空,这个节点没有线程引用
        node.waitStatus = Node.CANCELLED;

        // If we are the tail, remove ourselves.
       node为尾节点,cas后,将前继节点作为尾结点
        if (node == tail && compareAndSetTail(node, pred)) {
        	// 设置前继的下个节点,即node为null
            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;
            // 前继节点的下一个节点即node变成node的后续节点
            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 {
            	// 直接唤醒node的后续节点,通过LockSupport类的调用
                unparkSuccessor(node);
            }
			// node的后续指向自己,没有引用,方便回收
            node.next = node; // help GC
        }
    }

后话

本文大概介绍了一些AQS的原理以及AQS的常量和一些模板方法,并详细展开acquire 方法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值