AbstractQueuedSynchronizer (队列同步器)源码解析

AbstractQueuedSynchronizer (队列同步器)源码解析

结构

AbstractQueuedSynchronizer 是一个抽象的同步器,内部结构使用了双向链表(同步队列) + 单链表 (条件队列),在一般的情况下都只会使用同步队列,设计到阻塞队列 BlockQueue 时,会使用到条件队列,所以说 AbstractQueuedSynchronizer 是一个强大的抽象类,涵盖了并发时涉及到的各个方面。下面只会涉及到同步队列的使用

1. AbstractQueuedSynchronizer 结构

根据图片上可以看出,AbstractQueuedSynchronizer 使用了 head、tail 两个节点,然后实现了静态内部类 Node。

2. Node 结构

Node 的结构如下,主要的属性包括了 waitStatus(该节点或者后继节点的状态,后面使用 ws 作为简称)、thread(当前线程),通过将这两个属性作为参数构造 Node 节点。

3. 同步队列结构

根据图片可以看出,同步队列的结构中 head 节点中的 thread=null、ws=0,只是作为队列的队首。不参与实际的阻塞、唤醒操作。

重点方法

下面会根据获取锁、释放锁这两个方面去看AQS的整个流程。下面采用的是非公平模式。

lock (获取锁)

1. AbstractQueuedSynchronizer#acquire(int arg)

public final void acquire(int arg) {
	/**
	 * 1. 首先尝试获取锁
	 * 2. 获取不到则会将当前线程作为参数封装成 node,进入到同步队列中
	 * 3. node 作为参数入队后,会再次尝试获取,获取不到时,则会阻塞
	 */
	if (!tryAcquire(arg) &&
			acquireQueued(addWaiter(AbstractQueueSync.Node.EXCLUSIVE), arg))
		selfInterrupt();
}

2. tryAcquire(arg)

tryAcquire(arg)方法需要由子类进行实现,因为不同模式下的尝试获取操作不一致,这里体现了模板方法的设计思想,例如非公平模式下,ReentrantLock.NonfairSync#tryAcquire(args):
1. 首先获取当前锁的状态 state,没有被持有 state = 0,否则为重入多少次
2. state = 0: 直接 CAS 设置当前锁被持有,并且设置该把锁的独占线程为当前线程
3. state != 0: 查看当前锁的持有线程是否是当前线程,如果是,那么 state + 1, 否则获取失败锁。

// 非公平模式下的 tryAcquire
final boolean nonfairTryAcquire(int acquires) {
	// 获取当前线程
	final Thread current = Thread.currentThread();
	// 当前锁的状态
	int c = getState();
	// 没有被持有,直接CAS设置该锁被当前线程持有
    // 并设置独占线程 exclusiveOwnerThread 属性为当前线程
	if (c == 0) {
		if (compareAndSetState(0, acquires)) {
			setExclusiveOwnerThread(current);
			return true;
		}
	}
	// 表示重入,增加重入次数
	else if (current == getExclusiveOwnerThread()) {
		int nextc = c + acquires;
		if (nextc < 0) // overflow
			throw new Error("Maximum lock count exceeded");
		setState(nextc);
		return true;
	}
	// 获取失败,返回false
	return false;
}
// 公平模式
 protected final boolean tryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	int c = getState();
	if (c == 0) {
		// 表示队列中存在着非当前线程的节点,存在则返回true,获取失败
		if (!hasQueuedPredecessors() &&
				compareAndSetState(0, acquires)) {
			setExclusiveOwnerThread(current);
			return true;
		}
	}
	else if (current == getExclusiveOwnerThread()) {
		int nextc = c + acquires;
		if (nextc < 0)
			throw new Error("Maximum lock count exceeded");
		setState(nextc);
		return true;
	}
	return false;
}

3. tryAcquire(arg)

tryAcquire(arg)返回false,表示获取失败,继续执行acquireQueued(addWaiter(AbstractQueueSync.Node.EXCLUSIVE), arg))addWaiter(AbstractQueueSync.Node.EXCLUSIVE), arg) 表示将 node 添加到队列的尾部。

private AbstractQueueSync.Node addWaiter(AbstractQueueSync.Node mode) {
	AbstractQueueSync.Node node = new AbstractQueueSync.Node(Thread.currentThread(), mode);
	AbstractQueueSync.Node pred = tail;
	// 如果队列不为空,那么直接 CAS 将当前构造后的节点入队,将 node 设置为 tail
	if (pred != null) {
		node.prev = pred;
		if (compareAndSetTail(pred, node)) {
			pred.next = node;
			return node;
		}
	}
	// 走到这里表示,队列为空或者CAS失败,结果都是入队失败,那么继续将 node 进行入队操作
	enq(node);
	return node;
}
//初始化队列然后再次进行入队操作
private AbstractQueueSync.Node enq(final AbstractQueueSync.Node node) {
	for (; ; ) {
		AbstractQueueSync.Node t = tail;
		// 尾节点为空,表示队列是空,那么需要初始化节点,也就是 head 节点
		// 这里体现了懒加载的机制,当需要插入节点时,再进行初始化
		if (t == null) {
			if (compareAndSetHead(new AbstractQueueSync.Node()))
				tail = head;
		} else {
			// 出现的情况为 tail 不为空,然后将 node 设置为尾节点,并将原来的
			// tail 节点设置为 node 的前一个节点
			node.prev = t;
			if (compareAndSetTail(t, node)) {
				t.next = node;
				return t;
			}
		}
	}
}

将 node 节点添加进同步队列后,addWaiter(AbstractQueueSync.Node.EXCLUSIVE), arg)返回 node,继续执行从同步队列中获取锁,返回结果为当前线程是否中断,如果为true,表示中断,那么会执行selfInterrupt();,再次中断。

4. acquireQueued(final AbstractQueueSync.Node node, int arg)

acquireQueued(final AbstractQueueSync.Node node, int arg) 再次尝试从同步队列头部中获取锁,获取失败,则会执行将当前线程阻塞,并且检查中断状态。

final boolean acquireQueued(final AbstractQueueSync.Node node, int arg) {
	boolean failed = true;
	try {
		boolean interrupted = false;
		// 自旋
		for (; ; ) {
			// 1. 获取当前节点 node 的前驱节点 p
			final AbstractQueueSync.Node p = node.predecessor();
			// 2. 如果 p == head,那么当前线程再次尝试获取锁
			if (p == head && tryAcquire(arg)) {
				// 2.1 当前线程获取锁成功,那么就会将 head 指向 node,并且将
				// head 的属性值都初始化,保证 head 节点不存在实际的属性值
				setHead(node);
				p.next = null; // help GC
				failed = false;
				System.out.println(Thread.currentThread().getName() + "成功被唤醒");
				return interrupted;
			}
			/**
			 *
			 * 3. 走到这里,表示 p != head 或者 尝试获取线程失败,
			 * 那么需要将 p 节点的 waitStatus 状态修改为后继节点等待被唤醒 SIGNAL,接着将 node 指向的当前线程给阻塞
			 * shouldParkAfterFailedAcquire: 这里需要进入两次这个方法设置 waitStatus 值,因为初始值为 0, 所以该方法
			 * 会返回 false,那么继续自旋,等到下一步这里时, ws 的值已经被设置为 SIGNAL = -1,那么会返回 true,接着再
			 * 阻塞当前线程。为什么会进入两次判断呢,因为在 unlock 时,会唤醒线程,接着将队列中的唤醒节点 node 作为 head,并使得
			 * ws = 0,等到下一次其他线程获取时,可以保证 ws = 0。不用再去其他方法中修改 ws 值。
			 */
			if (shouldParkAfterFailedAcquire(p, node) &&
					// 将当前线程阻塞
					parkAndCheckInterrupt())
			interrupted = true;
		}
	} finally {
		if (failed) {
			// 当线程中断时,需要取消 node 节点获取
			System.out.println("线程中断,取消当前线程获取" + Thread.currentThread().getName());
			cancelAcquire(node);
		}
	}
}

5. shouldParkAfterFailedAcquire(p, node)

设置前驱节点的 ws 状态为SIGNAL。如果是第一次进入该方法时,如果 prev 的 ws 不为 SIGNAL,那么返回 false,并将ws 设置为SIGNAL,继续进行自旋,再次判断队列中 node 节点的前驱节点为 head,如果是,那么会再次进行尝试获取锁,否则进入 shouldParkAfterFailedAcquire,因为第一次已经将 ws 设置为 SIGNAL,那么这一次会返回true。接着执行 parkAndCheckInterrupt() 阻塞当前线程。

private static boolean shouldParkAfterFailedAcquire(AbstractQueueSync.Node pred, AbstractQueueSync.Node node) {
	// pred 节点的 ws
	int ws = pred.waitStatus;
	if (ws == AbstractQueueSync.Node.SIGNAL)
		// 直接返回
		return true;
	// 当前 ws = cancelled,循环从后向前遍历,跳过 cancelled 的节点
	if (ws > 0) {
		do {
			node.prev = pred = pred.prev;
		} while (pred.waitStatus > 0);
		pred.next = node;
	} else {
		/**
		 * 因为当前 node 需要被唤醒,所以如果 ws 不是 cancelled 的节点,那么设置 pred 的 ws 为唤醒
		  */
		compareAndSetWaitStatus(pred, ws, AbstractQueueSync.Node.SIGNAL);
	}
	return false;
}

6. parkAndCheckInterrupt()

parkAndCheckInterrupt(): 阻塞当前线程并返回线程的中断状态。

private final boolean parkAndCheckInterrupt() {
	System.out.println(Thread.currentThread().getName() + "阻塞,等待被唤醒");
	// 当前线程阻塞
	LockSupport.park(this);
	// 返回当前线程是否中断状态
	return Thread.interrupted();
}

unlock (释放锁)

1. release(int arg)

release(int arg) : 释放锁,tryRelease(arg)也是一个抽象方法,交给子类实现,可以看下ReentrantLock#tryRelease(int releases)

public final boolean release(int arg) {
	System.out.println(Thread.currentThread().getName() + "开始释放锁");
	if (tryRelease(arg)) {
		// 释放成功
		AbstractQueueSync.Node h = head;
		// h.waitStatus 为 0,表示为初始状态,这里是不是初始状态
		if (h != null && h.waitStatus != 0) {
			// 唤醒 head 节点的一个后继节点
			unparkSuccessor(h);
		}
		return true;
	}
	return false;
}

2. tryRelease(int releases)

tryRelease(int releases):尝试释放锁,当前锁的重入数 state - 1,首先判断锁的持有线程是否为当前线程,然后判断锁的重入次数是否已经为0,如果为0,表示锁已经完全释放,设置锁的状态,返回 true,否则返回 false。

protected final boolean tryRelease(int releases) {
	int c = getState() - releases;
	// 判断当前线程是否和锁的持有线程相等,如果不相等,表示这个锁出现了问题,抛出异常
	if (Thread.currentThread() != getExclusiveOwnerThread())
		throw new IllegalMonitorStateException();
	boolean free = false;
	if (c == 0) {
		free = true;
		// 锁释放,设置独占线程为 null
		setExclusiveOwnerThread(null);
	}
	// 设置 state = state - 1,state 不为0,则返回true,继续被持有线程持有,释放不完全
	setState(c);
	return free;
}

3. unparkSuccessor(h)

unparkSuccessor(h):从同步队列 head 节点后第一个状态正常的节点进行唤醒。

/**
 * 唤醒 node 的后继节点,正常进入此处为 head 节点,除了中断操作外
 * @param node the node
 */
private void unparkSuccessor(AbstractQueueSync.Node node) {
	/*
	 * ws < 0, 表示后继节点需要被唤醒, 然后将 CAS 将 ws 置为初始值 0,
	 * 设置 ws 为 0,因为 node 的 ws < 0 表示的是node 的下一个节点可以被唤醒,
	 * 那么就先要将当前 node 的 ws 状态进行修改, 方便节点被唤醒后,在方法
	 * #acquireQueued(final AbstractQueueSync.Node node, int arg)中不需要设置前驱节点 p 的 ws = 0,帮助GC回收
	 */
	int ws = node.waitStatus;
	// ws < 0: 表示不是要被取消的节点
	if (ws < 0) {
		compareAndSetWaitStatus(node, ws, 0);
	}

	/*
	 * 这里 s 节点为需要被唤醒的节点,也就是为 node 的第一个后继正常状态的节点
	 * 将等待状态为 cancelled 的节点都进行出队
	 * ws > 0:CANCELLED = 1: 表示状态为取消,
	 * 后面的意思为:找到 node 的后继节点 s ,如果 s 不是被标识为取消,那么就将 s 对应的线程唤醒
	 * 如果是 null 或者为取消状态,那么就从 tail -> node 中,找到离 node 最近的一个不是取消状态的节点,然后将该节点对应的线程唤醒
	 */
	AbstractQueueSync.Node s = node.next;
	if (s == null || s.waitStatus > 0) {
		s = null;
		// 从等待队列的尾部进行过滤
		for (AbstractQueueSync.Node t = tail; t != null && t != node; t = t.prev) {
			// waitStatus <=0 的状态有 SIGNAL = -1; CONDITION = -2;PROPAGATE = -3;
			if (t.waitStatus <= 0) {
				s = t;
			}
		}
	}
	if (s != null) {
		// 唤醒 s.thread 节点
		LockSupport.unpark(s.thread);
		System.out.println(s.thread.getName() + "节点被唤醒");
	}
}

上述的流程可以理解为 lock 与 unlock 操作之间的一个交替行为,当锁被持有时,需要 unlock 释放锁,然后唤醒阻塞线程继续执行。

测试

手动将ReentrantLock类和AbstractQueuedSynchronizer复制一份到本地,进行调试,然后使用5个线程去争夺锁,观察打印的结果。

模拟非公平锁的获取锁和释放锁的过程

package com.rule.concurrent.aqs;

public class AQSDemo {

    private static ReentrantLock reentrantLock = new ReentrantLock(false);

    public static void toThisMethod() {
        System.out.println(Thread.currentThread().getName() + "开始进入该方法");
        reentrantLock.lock();
        // 模拟业务代码执行
        try {
            Thread.sleep(100);
        } catch (Exception e) {
            e.printStackTrace();
        }
        reentrantLock.unlock();
        System.out.println(Thread.currentThread().getName() + "开始执行结束该方法");
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            // 5个线程去争夺锁
            new Thread(AQSDemo::toThisMethod, "thread-" + i).start();
        }
    }
}
执行结果

同步队列中的顺序 3->4->2->1,然后释放锁的顺序为 0->2->1->3->4,这种情况就反应出了不公平的线程,同步队列中的入队顺序不是最后释放锁的顺序。

模拟公平锁的获取锁和释放锁的过程

执行结果

同步队列中的顺序 1->3->2->4,然后释放锁的顺序为 0->2->1->3->4,这种情况就反应出了不公平的线程,同步队列中的入队顺序不是最后释放锁的顺序。

猜你喜欢

转载自blog.csdn.net/LarrYFinal/article/details/118443430