Java并发编程之一张图理解ContDownLatch

Java并发编程之你们常说的CAS到底是个啥?!.
Java并发编程之一张图理解ReentrantLock.
前面的文章理解了java中实现的乐观锁CAS思想,也理解了ReentrantLock中AQS的思想,今天主要介绍JUC中常用的几个工具的实现原理,看完后会发现java中的并发控制工具实现原理大同小异。JUC工具主要介绍以下三种,本文主要讲解ContDownLatch的实现原理。

  • ContDownLatch
  • Semphore
  • CyclicBarrier

如果你还没有使用过相关工具,建议先快速过一遍下面两个博客,了解工具的API使用。
Java并发编程之CountDownLatch.
ava并发编程之Semaphore.

ContDownLatch

首先看图。
在这里插入图片描述

1.new CountDownLatch(count)

//CountDownLatch
public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

//Sync
Sync(int count) {
    setState(count);
}
protected final void setState(int newState) {
    state = newState;
}

在初始化对象的构造方法中,对AQS进行了初始化,并将入参count赋值给AQS中的state

2.await() - 1

//CountDownLatch
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
//AbstractQueuedSynchronizer
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

首先调用tryAcquireShared看当前AQS中state是否为0,也就是其他子线程是否执行完毕,如果state不为0,调用doAcquireSharedInterruptibly(arg);

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    //加入双向链表
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
        	//获取当前节点的上一个节点
            final Node p = node.predecessor();
            if (p == head) {
            	//state是否为0
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                	//是
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

addWaiter(Node.SHARED);:将当前线程构建为一个Node节点,用来构建双向链表(当前线程第一个进入AQS),或者用于将当前线程加入到双向链表中。
之后判断当前state是否为0,也就是是否所有线程都调用了countDown,假设当前state不为0,进行阻塞parkAndCheckInterrupt()。接下来看countDown()中是怎样唤醒的

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

3.countDown()

//CountDownLatch
public void countDown() {
    sync.releaseShared(1);
}

调用AQS的releaseShared方法

//AbstractQueuedSynchronizer
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
//CountDownLatch
protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

tryReleaseShared中CAS操作将当前AQS队列中的state-1,操作成功后,返回state == 0,如果等于0,说明当前需要执行的所有线程全部countDown完毕。则执行doReleaseShared();方法,释放共享锁(多个线程可同时访问)。

private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
        	//头结点的等待状态
            int ws = h.waitStatus;
            //SIGNAL = -1 代表需要被唤醒
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

方法中,首先取到头节点,判断等待状态,是否需要被唤醒,是则调用unparkSuccessor(h);方法,来唤醒头节点的下一个节点。

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
	//头节点的下一个节点。
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        //遍历找到一个waittatus<0的节点,需要被唤醒的节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
    	//唤醒
        LockSupport.unpark(s.thread);
}

找到头节点的下一个节点ss为空或者大于0(当前线程已经被中断),则遍历找到一个需要被唤醒的node节点,赋值给s,最后调用LockSupport.unpark(s.thread);唤醒。

下面看被唤醒的线程都做了什么操作。

2.await() - 2

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            /************阻塞在这里。************/
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

上面讲到,threadA阻塞在上面代码所示位置,当被最后一个调用countDown的线程唤醒时,会再次进入自旋,这次再调用int r = tryAcquireShared(arg);state状态就是0了,返回1,执行setHeadAndPropagate(node, r);方法。

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    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();
    }
}

final boolean isShared() {
    return nextWaiter == SHARED;
}

在这里,首先将当前节点设置为头节点,然后找当前节点的下一个节点,如果为空或者下一个节点的nextWaiter是一个共享节点SHARED(多个线程await指向同一个SHARED),证明下个线程也需要被唤醒。
(因为CountDownLatch可以同时await多个,需要全部被唤醒)。
调用doReleaseShared,这个方法和countDown()中的一样,当前头节点的下个节点。setHead(node);,因为当前线程节点已经被设置为头节点,那么下个节点,就是头节点的下个节点。

继续回到本文 2.await() - 2中第一个代码片段中的标记点
/************阻塞在这里。************/

如果双向链表中阻塞了多个线程节点,则唤醒过程是一个一个从头节点到尾节点唤醒。

本文结束。
后续会陆续以画图+阅读源码的方式来搞懂JUC中其他工具的实现原理。
敬请期待。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程大帅气

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

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

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

打赏作者

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

抵扣说明:

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

余额充值