7.AQS源码分析之独占式获取锁和释放锁

一.AbstraactQueuedSynchronizer(AQS)
1.队列同步器,提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架
2.基于模板方法模式
3.使用方式:继承该类作为一个内部辅助类实现同步原语 并重写指定方法
4.实现思路:AQS内部维护一个CLH队列来管理锁 双向链表的队列。线程会首先
		   尝试获取锁,若失败,则将当前线程以及等到状态等信息包装成一
		   个Node节点,添加到同步队列中 接着不断尝试获取当前锁(前
		   提条件:当前节点是head节点的直接后继才会尝试),若失败会
		   阻塞自己,直至被唤醒,而当持有锁的线程被释放时,会唤醒队列
		   中的后继线程
5.实现的三件事:同步状态的管理,线程的阻塞与唤醒 同步队列的维护
二.Node节点类
  	/**
     * 总的来说;保存节点状态,前驱节点 后继节点 线程
     */
    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;

        /**
         * 等待状态:条件等待 当前线程在Condition队列当中
         */
        static final int CONDITION = -2;

        /**
         * 等待状态:传播 用于将唤醒后继线程传递下去 是为了完善和增强共享锁的唤醒机制 在一个节点成为头结点之前 是不会变成此状态
         */
        static final int PROPAGATE = -3;

        /**
         * 等待状态  0:表示无状态
         */
        volatile int waitStatus;

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

        /**
         * 后继节点
         */
        volatile Node next;

        /**
         * 节点对应的线程
         */
        volatile Thread thread;

        /**
         * 等待队列中的后继节点
         */
        Node nextWaiter;

        /**
         * 判断当前节点是否在共享模式下等待
         */
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        /**
         * 获取前驱节点 若为空抛出空指针异常
         * @return the predecessor of this node
         */
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null) {
                throw new NullPointerException();
            }
            else {
                return p;
            }
        }

        Node() {
        }

        /**
         * addWaiter会使用此构造方法
         * @param thread
         * @param mode
         */
        Node(Thread thread, Node mode) {
            nextWaiter = mode;
            this.thread = thread;
        }

        /**
         * Condition会调用此函数
         * @param thread
         * @param waitStatus
         */
        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
        /**
         * 状态分析:0和CONDITION为始态,CANCELLED为终态 0状态同时也可以是节点生命周期的终态
         */
    }
三.属性定义
 	/**
     * 逻辑上意义:当前持有锁的线程 本身并不会存储线程信息 延迟初始化
     */
    private transient volatile Node head;

    /**
     * 当一个线程无法获取锁而被加入到同步队列中,会用CAS来设置尾结点tail为当前线的对应节点
     */
    private transient volatile Node tail;

    /**
     * 同步状态 volatile:保证其他线程可见性  0 表示锁可获取状态
     */
    private volatile int state;
    /**
     * 线程自旋的等待时间 微秒
     */
    static final long spinForTimeoutThreshold = 1000L;
     /**
     * CAS操作
     */
    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);
        }
    }
    /**
     * CAS设置头结点 只被enq()方法使用
     */
    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }

    /**
     * CAS设置尾结点 仅被enq()方法使用
     */
    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

    /**
     * CAS等待状态
     */
    private static final boolean compareAndSetWaitStatus(Node node, int expect, int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update);
    }

    /**
     * CAS设置后继节点
     */
    private static final boolean compareAndSetNext(Node node, Node expect, Node update) {
        return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
    }
四.独占式获取同步状态

1.模板方法acquire(int arg)

/**
     * 独占式获取同步状态
     * 首先执行tryAcquire()方法,尝试获取锁
     * 获取失败 进入到addWaiter()方法,构造同步节点(独占式Node.EXCLUSIVE),将该节点添加到同步队列的尾部 并返回此节点 进入到acquireQueued()方法
     * acquireQueued()方法 这个新节点以死循环的方式获取同步状态,若获取不到则阻塞节点中的线程,阻塞后的节点等待前驱节点来唤醒或阻塞线程被中断
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
            selfInterrupt();
        }
    }

2.tryAcquire(int arg)

 	/**
     * 独占式的获取锁 实现该方法需要查询当前状态并判断当前状态的是否符合预期,再进行CAS设置同步状态
     */
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

4.addWaiter(Node node)

	/**
     * 将构造的同步节点加入到同步队列中
     * 使用链表的方式将Node节点添加到队列尾部,若tail的前驱节点不为空(队列不为空)则进行CAS添加到尾部
     * 如果更新失败(存在并发竞争) 进入enq()方法进行更新
     * @param mode Node.EXCLUSIVE 表示独占, Node.SHARED 表示共享
     */
    private Node addWaiter(Node mode) {
        //生成当前线程的节点
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
        if (pred != null) {
            //尾结点存在 将新生成的节点添加在尾结点tail后面
            node.prev = pred;
            //CAS操作将尾结点指向新生成的节点 之前尾结点的后继指向新生成的节点
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //尾结点不存在 表示同步队列还没有初始化 将新生成的节点添加到尾结点 通过自旋的方式进入队列
        enq(node);
        return node;
    }

5.enq(Node node)

	/**
	 * 以自旋的方式将节点添加到同步队列中
     * 该方法使用自旋的方式来保证向队列中添加Node
     * 若队列为空 ,则把当前Node设置成头结点
     * 若队列不为空,泽祥队列尾部添加Node
     * 该方式还是条件队列转换为同步队列的方法
     */
    private Node enq(final Node node) {
        //保证并发是能够正确的将node添加到队列尾端
        for (; ; ) { // CAS自旋方式 - 死循环
            Node t = tail;
            //尾结点不存在
            if (t == null) {
                //CAS操作生成一个默认的节点作为尾节点
                if (compareAndSetHead(new Node())) {
                    tail = head;
                }
            }
            else {
                //node节点的前驱节点指向尾结点
                node.prev = t;
                //CAS操作将尾结点指向node节点
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

6.acquireQueued(Node mode,int arg)

	/**
     * 以循环的方式获取同步状态 若获取不到则阻塞节点中的线程 阻塞后的节点等待前驱节点来唤醒或阻塞线程被中断
     * 只有前驱节点是头结点,才能获取同步状态
     */
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (; ; ) {
                //获取当前节点的前驱节点
                final Node p = node.predecessor();
                //检测P是否是头结点 若是 再次调用tryAcquire方法 尝试获取锁
                if (p == head && tryAcquire(arg)) {
                    //是头结点 返回true(获取同步状态成功了) 将当前节点设置为头结点
                    setHead(node);
                    p.next = null; // 便于垃圾
                    failed = false;
                    return interrupted;
                }
                //若P节点不是头结点 或者tryAcquire返回false 获取失败
                //判断node节点是否应该被阻塞 若应该被阻塞 那么阻塞node节点 并检测中断状态
                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
                    //若又中断 设置中断状态
                    interrupted = true;
                }
            }
        } finally {
            if (failed) {
                cancelAcquire(node);
            }
        }
    }

7.shouldParkAfterFailedAcquire(Node pred, Node node)

	/**
     * 获取前驱节点的状态ws
     * ws = SINGLE 表示可以被前驱节点唤醒 当前线程成可以被挂起 等待前驱节点唤醒 返回true
     * ws>0 前驱节点取消 并循环检查此前驱节点之前所有连续取消的节点 并返回false(不能挂起)
     * 尝试将当前节点的前驱节点的等待状态设置为SINGLE
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //获取前驱节点的状态
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
        //若前驱节点状态为SINGLE 则当前线程可以被阻塞
        {
            return true;
        }
        if (ws > 0) {
            //前驱节点的状态为CANCELLED 那就一直向前遍历 知道找到一个正常等待的状态
            do {
                node.prev = pred = pred.prev;
            }
            while (pred.waitStatus > 0);
            //并将当前Node排在后面
            pred.next = node;
        }
        else {
            //前驱节点正常 CAS修改前驱节点的状态为SIGNAL
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

8.parkAndCheckInterrupt()

	/**
     * 检测线程是否被中断
     */
    private final boolean parkAndCheckInterrupt() {
        //阻塞当前线程
        LockSupport.park(this);
        //判断是否中断来唤醒的
        return Thread.interrupted();
    }
五.独占式释放锁

1.release(int arg)

/**
     * 独占锁释放同步状态 释放同步状态之后 将同步队列中的第一个节点包含的线程唤醒
     */
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0) {
                //唤醒后继线程
                unparkSuccessor(h);
            }
            return true;
        }
        return false;
    }

2.unparkSuccessor(Node node)

	/**
     * 使用LockSupport.unpark(Thread thread)来唤醒被阻塞的线程
     * @param node the node
     */
    private void unparkSuccessor(Node node) {

        int ws = node.waitStatus;
        if (ws < 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);
        }
    }
八.总结

在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程会被加入到队列中并在队列中进行自旋,移除队列(或停止自旋)的前提条件是前驱节点为头结点,并且成功获取了同步状态。在释放同步状态时,同步器调用tryRelease(int arg)方法释放同步状态,然后唤醒节点的后继节点。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值