AbstractQueuedSynchronizer 队列同步器(AQS) 介绍

@[TOC](AbstractQueuedSynchronizer 队列同步器(AQS) 介绍)


一. 队列同步器介绍

队列同步器是用来构建锁的基础框架,如 ReentrantLock 和 ReentrantReadWriteLock 中的同步器 Sync 就是对队列同步器的具体实现。同步器 Sync 帮助锁隐藏了实现细节以及简化了锁的实现,开发者只需要关心锁的具体使用方法就行。

队列同步器内核是使用了一个 volatile 修饰的 int 成员变量来表示同步状态,以及一个 FIFO 队列来完成线程的排队工作。同时,队列同步器还支持独占式和共享式地获取同步状态,是读写锁的核心实现方法。

1. 队列节点等待状态

同步队列是存在于队列同步器的一个 FIFO 模式的双向队列,队列中会有一个或多个节点来管理线程的队列状态,同时队列同步器中会记录头结点和尾节点的地址。同步中的节点有一个 waitStatus 属性来表示队列中节点保存的线程的等待状态,waitStatus 一共有五个值:

  • CANCELLED:值为 1,由于队列中等待的线程超时或者被中断,节点需要从队列中取消等待,如果节点进入了这个状态,该节点的状态不会再变化。
  • SIGNAL:值为 -1,如果当前节点被取消了或者释放了,并且后继节点是等待状态中,可以通知后继节点,使后继节点得以运行。
  • CONDITION:值为 -2,节点在等待队列中,节点线程等待在 Condition 上,当其他线程调用了 signal() 方法后,该节点将会从等待队列转移到同步队列中。
  • PROPAGATE:值为 -3,共享式同步状态会一直传播下去,只能用于头节点。
  • INITIAL:值为 0,初始状态。
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;

    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;
    }
}

二. 队列同步器的种类

1. 独占式同步

独占式同步就是同一时间只能有一个线程获取同步状态,后序的线程只能生成一个节点并加入到同步队列中。比如写锁,在对共享变量进行写操作的时候,只能有一个线程获取到写锁并操作。

(一)锁的获取

状态的获取依赖于自定义同步器对 tryAcquire() 方法的实现,队列同步器中 tryAcquire() 是没有具体的实现方法的。

在获取锁的方法中,主要包含了锁的获取,以及节点构造和加入同步队列等方法。在尝试获取锁失败以后,会通过 addWaiter() 构建一个节点,并且再次让头结点出列。。

  • tryAcquire(arg):尝试获取锁
  • addWaiter(Node.EXCLUSIVE):构造独占式同步节点,并加入队列的尾部。构造节点的方法传入了 Node.EXCLUSIVE 参数,代表使用独占式构造同步节点。
  • acquireQueued(addWaiter(Node.EXCLUSIVE), arg)):头节点尝试获取锁。
public final void acquire(int arg) {  
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  
        selfInterrupt();  
}
addWaiter() 构造节点

构造节点的方法主要有两个 addWaiter()enq() ,一个是构造节点,另一个是构造完节点以后添加到尾节点。在构造节点的时候,如果是有多个线程在争抢 addWaiter() 方法,则使用 CAS 来原子性地保证当前线程获取到的节点是尾节点。同时,保证其他线程在争抢失败以后,还能通过 enq() 中的自旋方式以及 CAS,来保证构造的节点能成功添加到队列中去。

/**
* 创建节点
*/
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // 尝试快速添加到尾部,如果失败了则会在 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;
        if (t == null) {
	        // 如果没有尾节点,则代表队列是空的,这时候需要新建一个节点
            if (compareAndSetHead(new Node()))  
                tail = head;  
        } else {
	        // 如果没有尾节点,还是将当前节点设置成尾节点
            node.prev = t;  
            if (compareAndSetTail(t, node)) {  
                t.next = node;  
                return t;  
            }  
        }  
    }  
}
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;
                failed = false;  
                return interrupted;  
            }

			// 如果走到这步则表示失败了,需要将线程阻塞,并返回阻塞状态
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())  
                interrupted = true;  
        }  
    } finally {
		// 最后如果获取同步状态失败,则取消获取锁,并将当前节点的等待状态变为 CANCELLED,等待移除
        if (failed)
            cancelAcquire(node);  
    }  
}
  • shouldParkAfterFailedAcquire():尝试获取锁失败以后,需要将当前节点的状态修改成 SIGNAL,表示此节点可以被通知到。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
	// 判断当前节点的前驱节点的状态
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)  
        return true;
    if (ws > 0) {
	    // 去除队列中状态 CANCELLED 的节点,直到遇到状态不为 CANCELLED 的节点
	    // 是一个去除无效节点的过程
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);  
        pred.next = node;  
    } else {
	    // 如果是其他状态,则将前驱节点设置为 SIGNAL 状态,代表后继的节点可以运行
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);  
    }  
    return false;  
}
  • parkAndCheckInterrupt():阻塞线程的方法
/**
* 阻塞当前线程
*/
private final boolean parkAndCheckInterrupt() {  
    LockSupport.park(this);  
    return Thread.interrupted();  
}

/**
* LockSupport 类中阻塞线程的方法
*/
public static void park(Object blocker) {
    Thread t = Thread.currentThread();  
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}

private static void setBlocker(Thread t, Object arg) {  
    UNSAFE.putObject(t, parkBlockerOffset, arg);  
}

(二)锁的释放

独占式节点锁的释放使用 release() 方法,其中 tryRelease() 也是依赖于自定义同步器的实现。

public final boolean release(int arg) {  
    if (tryRelease(arg)) {
	    // 将 head 指向的节点释放
        Node h = head;
        if (h != null && h.waitStatus != 0)
	        // 清空无效节点并唤醒后继节点
            unparkSuccessor(h);  
        return true;
    }  
    return false;  
}
unparkSuccessor() 唤醒后继节点

唤醒后继节点的过程会有一个清空队列中无效节点的过程,也就是之前新构造的节点尝试获取锁失败以后,使用 cancelAcquire(node) 方法会有将节点设置为 CANCELLED 状态,并且此节点会在这时候释放。

private void unparkSuccessor(Node node) {  
	// 如果节点的同步状态非 CANCELLED 状态,则需要将状态设置为初始化
    int ws = node.waitStatus;
    if (ws < 0)  
        compareAndSetWaitStatus(node, ws, 0);  

	// 通知后继节点,如果没有后继节点或者节点的状态是 CANCELLED 的,则从尾节点向前寻找非 CANCELLED 状态的节点,并且将这些 CANCELLED 的节点出列
    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;  
    }

	// 将中间 CANCELLED 状态的节点清空以后,如果还有后继节点,并且状态是可以被通知的,则需要唤醒后继节点的线程,让节点可以获取同步状态
    if (s != null)  
        LockSupport.unpark(s.thread);  
}

2. 独占式超时同步

独占式超时同步比独占式同步主要多了一个超时时间,如果超过了给定的超时限定时间,则会放弃获取同步状态。

public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();  
    return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);  
}

(一)锁的获取

doAcquireNanos() 尝试在限定时间内获取到锁
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;
                failed = false;  
                return true;            
            }

			// 判断是否超时
            nanosTimeout = deadline - System.nanoTime();  
            if (nanosTimeout <= 0L)  
                return false;

			// 如果超时了并且后继节点的状态已经被修改,则需要阻塞线程
			// spinForTimeoutThreshold 为 1000
            if (shouldParkAfterFailedAcquire(p, node) &&  
                nanosTimeout > spinForTimeoutThreshold)  
                LockSupport.parkNanos(this, nanosTimeout);  
            if (Thread.interrupted())  
                throw new InterruptedException();  
        }  
    } finally {  
        if (failed)  
            cancelAcquire(node);  
    }  
}
  • parkNanos():有时限的线程阻塞方法
public static void parkNanos(Object blocker, long nanos) {  
    if (nanos > 0) {  
        Thread t = Thread.currentThread();  
        setBlocker(t, blocker);  
        UNSAFE.park(false, nanos);  
        setBlocker(t, null);  
    }  
}

3. 共享式同步

共享式同步允许多个线程进行读操作,而如果是写操作,则会对其它线程进行阻塞。通过调用 acquireShared() 可以共享式地获取锁。

其中 tryAcquireShared() 只有在成功获取状态并且后继节点也可以成功获取的情况下才是正数。

public final void acquireShared(int arg) {  
    if (tryAcquireShared(arg) < 0)  
        doAcquireShared(arg);  
}

(一)锁的获取

共享式同步获取锁的方法 doAcquireShared() 其实和独占式同步获取锁的方法 acquireQueued() 的大部分内容一样,主要注意的是尝试获取锁之后,重新设置头节点的时候,会有一个唤醒后继共享式节点的过程 setHeadAndPropagate()

private void doAcquireShared(int arg) {  
    final Node node = addWaiter(Node.SHARED);  
    boolean failed = true;  
    try {  
        boolean interrupted = false;  
        for (;;) {  
            final Node p = node.predecessor();  
            if (p == head) {

				// 尝试获取锁,会返回一个 int 类型的值,如果大于等于 0,则表示获取成功
                int r = tryAcquireShared(arg);  
                if (r >= 0) {
	                // 获取到锁成功以后,需要将新加的节点设置成头节点,并且将后继节点设置成可传播状态
                    setHeadAndPropagate(node, r);  
                    p.next = null;
                    
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }  
            }

			// 如果走到这步则表示失败了,需要将线程阻塞,并返回阻塞状态
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())  
                interrupted = true;  
        }  
    } finally {  
        if (failed)  
            cancelAcquire(node);  
    }  
}
  • setHeadAndPropagate():将节点设置成头节点,并且唤醒后继等待状态中的节点。
private void setHeadAndPropagate(Node node, int propagate) {  
    Node h = head;
    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();  
    }
}

(二)锁的释放

public final boolean releaseShared(int arg) {  
    if (tryReleaseShared(arg)) {  
        doReleaseShared();  
        return true;    
    }  
    return false;  
}
  • doReleaseShared()
private void doReleaseShared() {  
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
	        // 如果头节点是可通知的,则需要唤醒头节点的后继节点,保证后继节点能获取到锁
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {  
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))  
                    continue;

				// 唤醒后继节点
                unparkSuccessor(h);
            }

			// 如果节点是初始化状态的,则需要等待变成 PROPAGATE 状态才能释放
            else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))  
                continue;
        }

        if (h == head)
            break;  
    }  
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瞎叨叨的一天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值