Java并发编程-再谈 AbstractQueuedSynchronizer 1 :独占模式(1)
- UID
- 1066743
|
Java并发编程-再谈 AbstractQueuedSynchronizer 1 :独占模式(1)
独占模式acquire实现流程有了上面的这些基础,我们看一下独占式acquire的实现流程,主要是在线程acquire失败后,是如何构建数据结构的,先看理论,之后再用一个例子画图说明。
看一下AbstractQuueuedSynchronizer的acquire方法实现流程,acquire方法是用于独占模式下进行操作的:
1
2
3
4
5
| public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
|
tryAcquire方法前面说过了,是子类实现的一个方法,如果tryAcquire返回的是true(成功),即表明当前线程获得了一个执行当前动作的资格,自然也就不需要构建数据结构进行阻塞等待。
如果tryAcquire方法返回的是false,那么当前线程没有获得执行当前动作的资格,接着执行”acquireQueued(addWaiter(Node.EXCLUSIVE), arg))”这句代码,这句话很明显,它是由两步构成的:
- addWaiter,添加一个等待者
- acquireQueued,尝试从等待队列中去获取执行一次acquire动作
分别看一下每一步做了什么。
addWaiter先看第一步,addWaiter做了什么,从传入的参数Node.EXCLUSIVE我们知道这是独占模式的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| 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 prev = tail;
if (prev != null) {
node.prev = prev;
if (compareAndSetTail(prev, node)) {
prev.next = node;
return node;
}
}
enq(node);
return node;
}
|
首先看第4行~第11行的代码,获得当前数据结构中的尾节点,如果有尾节点,那么先获取这个节点认为它是前驱节点prev,然后:
- 新生成的Node的前驱节点指向prev
- 并发下只有一条线程可以通过CAS算法让自己的Node成为尾节点,此时将此prev的next指向该线程对应的Node
因此在数据结构中有节点的情况下,所有新增节点都是作为尾节点插入数据结构。从注释上来看,这段逻辑的存在的意义是以最短路径O(1)的效果完成快速入队,以最大化减小开销。
假如当前节点没有被设置为尾节点,那么执行enq方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
|
这段代码的逻辑为:
- 如果尾节点为空,即当前数据结构中没有节点,那么new一个不带任何状态的Node作为头节点
- 如果尾节点不为空,那么并发下使用CAS算法将当前Node追加成为尾节点,由于是一个for(;;)循环,因此所有没有成功acquire的Node最终都会被追加到数据结构中
看完了代码,用一张图表示一下AbstractQueuedSynchronizer的整体数据结构(比较简单,就不自己画了,网上随便找了一张图):
|
|
|
|
|
|