zoukankan      html  css  js  c++  java
  • 线程同步器CountDownLatch

      Java程序有的时候在主线程中会创建多个线程去执行任务,然后在主线程执行完毕之前,把所有线程的任务进行汇总,以前可以用线程的join方法,但是这个方法不够灵活,我们可以使用CountDownLatch类,实现更优雅,而且使用线程池的话,可没有办法调用线程的join方法的呀!

    一.简单使用CountDownLatch

      直接使用线程:

    package com.example.demo.study;
    
    import java.util.concurrent.CountDownLatch;
    
    public class Study0215 {
        //这里相当于新建一个初始值为2的计数器
        private static volatile CountDownLatch countDownLatch = new CountDownLatch(2);
        
        public static void main(String[] args) throws InterruptedException {
            
            new Thread(()->{
                try {
                    Thread.sleep(1000);
                    System.out.println("线程一执行完毕");
                } catch (Exception e) {
                    
                }finally {
                    //每调用这个方法计数器减一
                    countDownLatch.countDown();
                }
                
            }).start();
            
            new Thread(()->{
                try {
                    Thread.sleep(1000);
                    System.out.println("线程二执行完毕");
                } catch (Exception e) {
                    
                }finally {
                    countDownLatch.countDown();
                }
                
            }).start();
            
            System.out.println("两个线程已经全部启动");
            //只要调用了这个方法之后,主线程会阻塞,直到计数器countDownLatch变成0就会返回
            countDownLatch.await();
            System.out.println("执行完毕");
            
        }
        
    }

      实际中尽量少直接操作线程,而是使用线程池:

    package com.example.demo.study;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class Study0215 {
        // 这里相当于新建一个初始值为2的计数器
        private static volatile CountDownLatch countDownLatch = new CountDownLatch(2);
    
        public static void main(String[] args) throws InterruptedException {
            //创建线程池
            ExecutorService pool = Executors.newFixedThreadPool(2);
            //将任务一丢进线程池
            pool.submit(() -> {
                try {
                    Thread.sleep(1000);
                    System.out.println("线程一执行完毕");
                } catch (Exception e) {
    
                } finally {
                    // 每调用这个方法计数器减一
                    countDownLatch.countDown();
                }
            });
            //任务二丢进线程池
            pool.submit(() -> {
                try {
                    Thread.sleep(1000);
                    System.out.println("线程二执行完毕");
                } catch (Exception e) {
    
                } finally {
                    countDownLatch.countDown();
                }
            });
    
            System.out.println("两个线程已经全部启动");
            // 只要调用了这个方法之后,主线程会阻塞,直到计数器countDownLatch变成0就会返回
            countDownLatch.await();
            System.out.println("执行完毕");
    
        }
    
    }

    二.await方法

      看下面的图,可以知道这个CountDownLatch类内部有个工具类Sync实现了AQS,然后CountDownLatch中的方法都是调用工具类Sync去操作的,emmm....跟前面说过的ReentrantLock类结构是一样的;

      我们看看CountDownLatch构造器传递的数其实就是设置AQS中state的值:

    //实际上调用把值传递给了Sync,也就是设置了AQS中的state
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
    Sync(int count) {
        setState(count);
    }

      

      我们再看看await方法:

    //当前线程调用了await方法之后,当前线程就会给阻塞,直到以下两种情况:
    //1.其他线程调用了countDown方法将计数器减到0之后,该线程就返回了;
    //2.其他线程调用了当前的线程的中断方法,当前线程抛出异常InterruptedException
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    
    public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
        //当前线程被中断就抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();
        //查看计数器中的值是不是0,不过不是0,就进入AQS等待队列等待;
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
    
    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }

    三.countDown方法

    public void countDown() {
        sync.releaseShared(1);
    }
    
    public final boolean releaseShared(int arg) {
        //tryReleaseShared方法返回false,说明当前计数器的值减一成功
        //返回true,说明计数器的值此时为0,那就要唤醒因为调用了CountDownLatch而阻塞的线程
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
    
    protected boolean tryReleaseShared(int releases) {
        //一个无限循环
        for (;;) {
            //获取state的值
            int c = getState();
            //如果state为0,返回false
            if (c == 0)
                return false;
            //否则就把state减一然后用CAS更新到state
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }

    四.getState方法

      这个方法获取计数器的值,其实就是获取AQS中的state的值;

    int getCount() {
        return getState();
    }
    protected final int getState() {
        return state;
    }

       其实CountDownLatch比较容易,功能和Thread的join方法一样,只不过更灵活,基于AQS实现,在初始化的时候设置state的值,当线程调用CountDownLatch的await方法的时候,当前线程就会被丢到AQS的阻塞队列挂起;然后当其他线程调用了countDown方法,其实就是将state减一,当state等于0的时候,就会唤醒所有因为调用await方法而阻塞的线程;

  • 相关阅读:
    将1、2、3、……、n这n个连续自然数分成g组,使每组的和相等。g组中个数最多的一组有几个?
    磁带机、驱动器、磁带库、机械手之间的区别
    Mysql基础命令
    pip 加速下载
    NBU命令之 nbftconfig :配置与光纤传输 (FT) 服务器和 SAN 客户端相关的属性
    IEDriverServer.exe驱动问题汇总
    系统集成项目管理工程师考试2020介绍
    LTO1,LTO2,LTO3,LTO4,LTO5 LTO6 磁带读写速度和兼容性及LTO6主要参数
    Mysql 备份方式 MySQL Agent & MySQL Enterprise Backup & Percona XtraBackup
    Netbackuk命令之bpclntcmd
  • 原文地址:https://www.cnblogs.com/wyq1995/p/12315072.html
Copyright © 2011-2022 走看看