Java多线程之CountDownLatch实现原理和源码分析(八)

章节概览:
1、概述

CountDownLatch是一个用来控制并发的很常见的工具,它允许一个或者多个线程等待其他的线程执行完其操作。比如我需要统计多篇文章中出现不同单词的数量,我们会为每篇文章分配一个线程进行统计,统计完成之后,会保存一个单词统计列表。等所有的统计线程都执行完成以后,对这些统计出来的结果用一个线程去汇总,这就可以使用CountDownLatch。


2、CountDownLatchDemo 描述

案例中,有2个线程,thread1,thread2。 主线程为main函数的线程。通过countDownLatch控制工具。必须等到thread1,thread2,执行完成在去执行main函数线程,即: System.out.println(“hello world CountDownLatchDemo”);

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException{

        CountDownLatch countDownLatch = new CountDownLatch(2);

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //do something
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread .currentThread().getName() + " is done");
                countDownLatch.countDown();
            }
        }, "thread1");

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //do something
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " is done");
                countDownLatch.countDown();
            }
        }, "thread2");


        thread1.start();
        thread2.start();

        countDownLatch.await();
        // 等待Thread1,Thread2执行完成以后,在去执行下面的任务
        System.out.println("hello world CountDownLatchDemo");
    }
}

输出结果:

thread1 is done
thread2 is done
hello world CountDownLatchDemo

3、CountDownLatch 构造函数和核心成员分析
public class CountDownLatch {
	// 构造函数十分简单,就是输入需要等待的线程个数计数器count,去初始化同步策略
	public CountDownLatch(int count) {
	       if (count < 0) throw new IllegalArgumentException("count < 0");
	       this.sync = new Sync(count);
	   }
	// CountDownLatch 的同步策略方式,其实现是通过AQS队列同步器实现的
	private final Sync sync;
}

4、Sync 源码分析
public class CountDownLatch {
    /**
     * Synchronization control For CountDownLatch.
     * Uses AQS state to represent count.
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;
        
        Sync(int count) {
            // 初始化设置state的状态值
            setState(count);
        }
	
	// 获取当前的Count的值
        int getCount() {
            return getState();
        }

       // 获取共享锁
        protected int tryAcquireShared(int acquires) {
            // 判断当前状态是否为 0 ,如果是0 ,返回1 ,不是的话返回 -1
            return (getState() == 0) ? 1 : -1;
        }

        // 释放共享锁
        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;
            }
        }
    }

5、countDown流程源码分析
5.1、释放当前持有的状态 java.util.concurrent.CountDownLatch#countDown
// 调用countDown方法,将当前的状态值count -1 
// 当前有线程执行完成,将释放当前持有的状态标记
public void countDown() {
        详情:java.util.concurrent.locks.AbstractQueuedSynchronizer#releaseShared
        sync.releaseShared(1);
    }
5.2、 java.util.concurrent.locks.AbstractQueuedSynchronizer#releaseShared

共享锁的释放,分为两个步骤进行释放。

  • 步骤一:尝试去释放当前共享状态,返回释放结果
  • 步骤二:如果返回结果为true,则唤醒后续节点
 public final boolean releaseShared(int arg) {
        // 释放当前持有的状态
        // 参考:5.3、 java.util.concurrent.CountDownLatch.Sync#tryReleaseShared 尝试释放锁
        if (tryReleaseShared(arg)) {
            // 唤醒后续节点
            doReleaseShared();
            return true;
        }
        return false;
    }
5.3、 java.util.concurrent.CountDownLatch.Sync#tryReleaseShared 尝试释放锁

释放当前持有的state的状态值

protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                // 获取当前的state的状态值
                int c = getState();
                // 如果当前的状态值 c == 0,直接返回false,不进行后续节点的唤醒操作
                if (c == 0)
                    return false;
                // 获取下一个state的状态值
                int nextc = c-1;
                // 通过cas设置下一个状态的值
                if (compareAndSetState(c, nextc))
                    // 如果下一个状态值为 0,说明当前调用线程是最后一个执行的线程,直接返回true,唤醒后续节点
                    return nextc == 0;
            }
        }
5.4、java.util.concurrent.locks.AbstractQueuedSynchronizer#doReleaseShared 唤醒后续节点
private void doReleaseShared() {
        // 死循环,出口为当前 h = head节点,结束
        // 出口可能存在的情况:
        // 1、h == null, h == tail 即 head 为最后的一个节点,无后续需要唤醒的节点了
        // h 依然持有当前线程,没有其他线程重置head节点
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                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
            }
            // 如果在修改head节点的过程中,有其他的线程获得了当前的资源,或者唤醒的线程获得当前资源
            // 则需要重新设置head节点的属性,并且进行后续节点的唤醒
            if (h == head)                   // loop if head changed
                break;
        }
    }


6、await 流程源码分析
6.1、 java.util.concurrent.CountDownLatch#await()
public void await() throws InterruptedException {
        // 获取共享可中断锁
        sync.acquireSharedInterruptibly(1);
    }
6.2、java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireSharedInterruptibly
 public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        // 如果当前线程被中断,直接抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();
        // 获取共享锁
        // 参考:6.3、java.util.concurrent.CountDownLatch.Sync#tryAcquireShared
        if (tryAcquireShared(arg) < 0)
            // 说明前置线程没有执行完成,需要进入等待状态
            
            doAcquireSharedInterruptibly(arg);
    }
6.3、java.util.concurrent.CountDownLatch.Sync#tryAcquireShared
protected int tryAcquireShared(int acquires) {
            // 如果当前状态为0,则返回 1,否则返回-1
            // 如果发返回为0,说明当前前置线程都已经执行完成,无需等待
            // 如果返回 -1,说明前置线程没有执行完成,需要进入等待状态。等待前面的线程执行完成
            return (getState() == 0) ? 1 : -1;
        }
6.4、java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireSharedInterruptibly

当前进行加入到AQS的队列同步器中。
这块代码和之前分析的读写锁的尝试获获取共享锁代码基本相同。唯一不同的是加了可中断的逻辑。
此处代码不做分析,如有疑问请参考:Java多线程之ReentrantReadWriteLock实现原理和源码分析(七)

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

至此,CountDownLatch 源码基本分析完成

7、总结

CountDownLatch 源码分析完成,主要通过两个方法进行控制,分别为:countDown,await。其内部源码实现逻辑和读写锁有点相似。比如初始化的count的值,相当于初始化了写锁。如果写锁的个数不为0,说明一直被占用,不能唤醒。而await,相当于读锁,尝试去获取读锁,获取不到,加入同步器队列,等待唤醒。同时它也是个共享锁,如果获得唤醒机会机会的话,它会唤醒后续的所有等待节点。就是我们说的:它允许一个或者多个线程等待其他的线程执行完其操作。如果疑问欢迎讨论

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值