首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

Java并发编程-再谈 AbstractQueuedSynchronizer 1 :独占模式(1)

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的整体数据结构(比较简单,就不自己画了,网上随便找了一张图):
返回列表