集合与多线程系列-AbstractQueuedSynchronizer(一)

1.简介

AbstractQueuedSynchronizer,简称AQS,采用模板方法设计模式,是实现锁(也可以是任意同步组件)的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。锁是面向使用者,它定义了使用者与锁交互的接口,隐藏了实现细节;同步器是面向锁的实现者,它简化了锁的实现方式,屏蔽了同步状态的管理,线程的排队,等待和唤醒等底层操作。AQS的核心是一个同步队列和一个volatile修饰的状态state,支持独占式操作和共享式操作,比较常见的ReentrantLock使用的是独占式,Semaphore、CountDownLatch等使用的是共享式。

2.AQS同步队列

AQS内部维护了一个CLH队列,用于管理同步状态及线程排队:
在这里插入图片描述

static final class Node {
        
        // 用于标记节点是共享模式
        static final Node SHARED = new Node();
        // 用于标记节点是独占模式
        static final Node EXCLUSIVE = null;
        // waitStatus为CANCELLED状态,表示当前的线程被取消、被打断或者获取超时了
        static final int CANCELLED =  1;
        // waitStatus为SIGNAL状态,表示当前节点的后继节点已经(或者很快会)通过park阻塞,因此当前节点释放或者取消的时候必须unpark它的后继节点
        static final int SIGNAL    = -1;
        // waitStatus为CONDITION状态,表示当前节点在 condition 队列中等待,直到状态在某个时间节点被转化为0前,它都不会最为同步队列的一个节点被使用
        static final int CONDITION = -2;
        // waitStatus为CONDITION状态,表示当前场景下后续的 acquireShared 能够得以执行,这是设置(仅适用于头节点)doReleaseShared保证继续传播
        static final int PROPAGATE = -3;
		// 节点等待状态
        volatile int waitStatus;

        // 前驱节点
        volatile Node prev;

        // 后继节点
        volatile Node next;

        // 被节点包装的线程
        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;
        }

		// 无参构造器,用于创建哨兵节点或者共享模式标记节点
        Node() {
        }
		// 用于同步队列创建新节点
        Node(Thread thread, Node mode) {
            this.nextWaiter = mode;
            this.thread = thread;
        }
		// 用于条件队列创建新节点
        Node(Thread thread, int waitStatus) {
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }
3.独占式获取锁
3.1 不响应线程中断
3.1.1 流程图

在这里插入图片描述

3.1.2 JDK源码
	public final void acquire(int arg) {
		// 步骤1:tryAcquire,尝试获取锁
		// 步骤2:addWaiter,把当前线程包装成Node,添加到同步队列队尾
		// 步骤3:acquireQueued,排队获取锁
		// 步骤4:selfInterrupt,判断排队获取锁过程中线程是否被中断,如果被中断,就再次中断当前线程
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
	// 步骤1:尝试获取锁,模板方法,具体实现由子类完成
	protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
	// 步骤2:把当前线程包装成独占模式的Node,添加到同步队列队尾
	private Node addWaiter(Node mode) {
		// 创建新节点
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
        	//尾节点不为空,说明同步队列已经初始化过,尝试CAS添加新节点到同步队列队尾,成功则返回新节点,失败则进入enq。
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 同步队列未初始化,或者上一步CAS操作失败,通过自旋添加新节点到同步队列队尾
        enq(node);
        // 返回新节点
        return node;
    }

	private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
            	// 同步队列未初始化,创建一个哨兵节点,CAS操作设置为同步队列队头,成功或者失败都重新进入for循环进行判断
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            	// 同步队列已初始化,CAS添加新节点到同步队列队尾,成功则返回新节点的前驱节点,失败则重新进入for循环进行判断
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
	// 步骤3:排队获取锁
	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 {
            if (failed)
                cancelAcquire(node);
        }
    }

	// 获取锁失败后判断当前线程是否可以park
	private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
		// 获取前驱节点状态
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            // 如果前驱节点状态为-1(SIGNAL),当前线程可以park
            return true;
        if (ws > 0) {
            // 如果前驱节点状态为1(取消状态),则一直查找到第一个非取消状态的节点,并删除中间的取消状态的节点,当前线程不可以park
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            // 如果前驱节点状态为0(默认)或者-3(PROPAGATE),CAS设置前继节点的状态为-1(SIGNAL),当前线程不可以park
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

	private final boolean parkAndCheckInterrupt() {
		// park当前线程,如果线程被唤醒或者被中断醒来,则返回当前线程时候被中断
        LockSupport.park(this);
        return Thread.interrupted();
    }
	// 步骤4:判断排队获取锁过程中线程是否被中断,如果被中断,就再次中断当前线程
	static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }
3.2 响应线程中断
3.2.1 流程图

在这里插入图片描述

3.2.2 JDK源码
	// 整个过程和不响应线程中断类似,区别就是响应线程中断,如果排队获取锁过程中被中断,就会抛出InterruptedException
	public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }
	private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        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;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    // 如果中间线程被中断,抛出InterruptedException
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
3.3 响应线程中断并判断超时
3.3.1 流程图

在这里插入图片描述

3.3.2 JDK源码
	// 整个过程和响应线程中断类似,区别就是会判断是否超时
	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;
        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)
                    // 细节:判断剩余时间如果大于spinForTimeoutThreshold(1000纳秒),则park当前线程,否则进入自旋判断,避免剩余时间过短,park到醒来整个过程时间过长导致超时判断不准确
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                	// 如果中间线程被中断,抛出InterruptedException
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
4.独占式释放锁
	public final boolean release(int arg) {
        if (tryRelease(arg)) {
        	// 尝试释放锁成功
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        // 尝试释放锁失败
        return false;
    }

	// 尝试释放锁,模板方法,具体实现由子类完成
	protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

	// 唤醒后继节点
	private void unparkSuccessor(Node node) {
        // 获取当前状态
        int ws = node.waitStatus;
        if (ws < 0)
        	// 当前状态小于0,则CAS操作将当前节点状态设置为0
            compareAndSetWaitStatus(node, ws, 0);

        // 获取后继节点
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
        	// 如果后继节点不存在或者状态为已取消,则从同步队列队尾往前一直找到当前节点后面的状态<=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);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值