• Java多线程同步工具类之CountDownLatch


    在过去我们实现多线程同步的代码中,往往使用join()、wait()、notiyAll()等线程间通信的方式,随着JUC包的不断的完善,java为我们提供了丰富同步工具类,官方也鼓励我们使用工具类来实现多线程的同步,今天我们就对其中CountDownLatch类的使用与底层实现进行分析与总结。

    一、CountDownLatch使用

    CountDownLatch其实可以看做一个计数器,统计多个线程执行完成的情况,适用于控制一个或多个线程等待,直到所有线程都执行完毕的场景,类似与Thread.join()的作用。下面我们通过一个简单的例子看下CountDownLatch的使用。

        public static void main(String[] args) {
            final CountDownLatch countDownLatch = new CountDownLatch(5);
    
            // 启动计数线程
            for (int i = 0; i < 5; i++) {
                new CountDownLatchThread(i, countDownLatch).start();
            }
    
            // 启动等待线程
            for (int i = 0; i < 5; i++) {
                new Thread() {
                    public void run() {
    
                        try {
                            countDownLatch.await();
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
    
                        System.out.println("计数完毕了," + Thread.currentThread().getName() + "等待线程执行");
    
                    }
                }.start();
            }
    
        }

    计数线程代码:

    public class CountDownLatchThread extends Thread {
    
        private CountDownLatch countDownLatch;
    
        private int name;
    
        private int count;
    
        public CountDownLatchThread(int name, CountDownLatch countDownLatch) {
            this.name = name;
            this.countDownLatch = countDownLatch;
            this.count = 0;
        }
    
        public void run() {
            try {
                for (int i = 0; i < 10; i++) {
                    Thread.sleep(100);
                    count++;
                }
                System.out.println(name + "号线程--" + Thread.currentThread().getName() + "--计数完成了");
                countDownLatch.countDown();
    
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    输出结果:

    1号线程--Thread-1--计数完成了
    0号线程--Thread-0--计数完成了
    4号线程--Thread-4--计数完成了
    2号线程--Thread-2--计数完成了
    3号线程--Thread-3--计数完成了
    计数完毕了,Thread-5等待线程执行
    计数完毕了,Thread-6等待线程执行
    计数完毕了,Thread-7等待线程执行
    计数完毕了,Thread-8等待线程执行
    计数完毕了,Thread-9等待线程执行

    通过上面的例子可以看到,利用CountDownLatch的countDown方法与await()方法,我们可以同步计数线程与等待线程,使等待线程在所有计数线程完成后再开始运行。

    二、CountDownLatch源码分析

    接下来我们对countDownLatch内部源码进行一下分析。

    1、CountDownLatch的构造。

    首先看下CountDownLatch的构造函数

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

    CountDownLatch的构造函数会接收一个count值做为计数器,也就是如果你需要等待N个线程执行结束,那这里就传入N。同时CountDownLatch会实例化一个Sync对象,这个Sync其实是CountDownLatch内部定义的一个继承自AbstractQueuedSynchronizer的实现类,所以CountDownLatch提供的同步和其他功能都是围绕Sync这个子类实现的,也就是基于AbstractQueuedSynchronizer类来实现的。

    我们来看下Sync这个类的定义

        private static final class Sync extends AbstractQueuedSynchronizer {
            private static final long serialVersionUID = 4982264981922014374L;
    
            /**
            1、设置AbstractQueuedSynchronizer中同步状态的值state,也就是计数器的值。
            2、这个值volatile变量,必须保证线程间的可见性;
            **/
            Sync(int count) {
                setState(count);
            }
    
            int getCount() {
                return getState();
            }
    
            //获取同步状态的值
            protected int tryAcquireShared(int acquires) {
                return (getState() == 0) ? 1 : -1;
            }
    
            protected boolean tryReleaseShared(int releases) {
                // 通过CAS操作改变同步状态值,保证同步状态的值state的线程安全。
                for (;;) {
                    int c = getState();
                    if (c == 0)
                        return false;
                    int nextc = c-1;
                    if (compareAndSetState(c, nextc))
                        return nextc == 0;
                }
            }
        }

    2、countDown方法

    首先我们看下countDown方法的源码

        
        public void countDown() {
           //改变同步状态值,线程执行完成时计数器减一
            sync.releaseShared(1);
        }

     AbstractQueuedSynchronizer类中releaseShared() 方法的源码

        public final boolean releaseShared(int arg) {
            // CountDownLatch定义的子类Sync实现,通过CAS操作改变State的值
            if (tryReleaseShared(arg)) {
                //State以递减为0,代表着所有执行线程执行完毕,共享模式下释放锁,那么等待线程就能够拿到锁往下执行。
                doReleaseShared();
                return true;
            }
            return false;
        }

    当调用CountDownLatch的countDown方法时,就会执行计数器进行减一操作,直到所有线程全部执行完毕,计算器为0时唤醒等待线程。

    AbstractQueuedSynchronizer中doReleaseShared方法是执行共享模式下释放锁的操作,从而让等待线程获取锁,继续向下执行。

    3、await方法

        public void await() throws InterruptedException {
            sync.acquireSharedInterruptibly(1);
        }

     AbstractQueuedSynchronizer类中acquireSharedInterruptibly() 方法的源码

        public final void acquireSharedInterruptibly(int arg)
                throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            //获取同步状态值
            if (tryAcquireShared(arg) < 0)
                //同步状态值即计数器的值不为0,等待线程共享模式下尝试获取锁,获取不到锁的话进入阻塞
                doAcquireSharedInterruptibly(arg);
        }

    await方法的实现也很明确,首页获取同步状态也就是计数器的值,如果为0即所有线程执行完毕返回1,否则返回-1的话,等待线程在共享模式下尝试获取锁,获取不到锁的话进入阻塞;

    AbstractQueuedSynchronizer中doAcquireSharedInterruptibly方法是执行共享模式下获取锁的操作;

    三、总结

    通过上面分析可以看到CountDownLatch是基于AbstractQueuedSynchronizer类实现的,一个非常实用的多线程控制工具类,它类似与一个计数器用来控制指定的线程等待,直到计数器归零。以上我们对CountDownLatch类的使用与核心方法的源码进行了一定的分析,其中如有不足与不正确的地方还望指出与海涵。

    关注微信公众号,查看更多技术文章。

  • 相关阅读:
    Thinking in java Chapter10 内部类
    策略设计模式 Strategy模式
    Thinking in java Chapter9 接口
    Thinking in java Chapter8 多态
    Thinking in java Chapter7 复用类
    mock服务
    文件的使用方式
    字符编码与文件处理方式
    各容器类型及内置方法
    python各数据类型及内置方式:
  • 原文地址:https://www.cnblogs.com/dafanjoy/p/10423219.html
Copyright © 2020-2023  润新知