多线程下的计数器

概述

juc是java的核心包,而juc下面的核心包之一应该就是locks了,而locks下最核心的类就是AbstractQueuedSynchronizer类(简称aqs),它可以说是多线程下所有锁相关类的父类,我们来看看它有多少子类。

在这里插入图片描述

可以看到有ReentrantLock,ReentrantReadWriteLock,Semaphore,CountDownLatch,ThreadPoolExecutor。ps: CyclicBarrier是采用ReentrantLock实现的。这次我们先学习一下多线程下计数器的相关使用,分别是CountDownLatch,CyclicBarrier,Semaphore。

CountDownLatch

作用:计数器

应用场景:

  1. 在主线程中等待所有线程完成之后做统计汇总(eg.开启若干个线程下载文件,需等待所有文件下载结束之后统计总下载文件大小)
  2. 所有线程等待主线程通知,通知后所有线程开始执行任务(eg.开启若干个线程下载文件,但是所有线程必须同时开始执行下载任务)

代码1:

public class TestCountDownLatch {

    // 请自行设置图片地址
    public static String[] targetUrls = new String[]{
            "http://xxxx/download/attachments/25559453/%E6%8A%B5%E6%89%A3%E5%88%B8.JPEG?version=1&modificationDate=1606189758000&api=v2",
            "http://xxxx/download/attachments/25562400/%E6%9C%AA%E5%91%BD%E5%90%8D%E6%96%87%E4%BB%B6.png?version=1&modificationDate=1608003595000&api=v2",
            "http://xxxx/download/attachments/15772822/image2020-6-15_18-16-16.png?version=1&modificationDate=1592216176000&api=v2",
            "http://xxxx/download/attachments/6675013/image2019-3-28%2016%3A4%3A35.png?version=1&modificationDate=1553760442000&api=v2",
            "http://xxxx/download/attachments/25562935/image2020-12-18_10-37-43.png?version=1&modificationDate=1608259064000&api=v2"
    };

    public static void main(String[] args) {
        TestCountDownLatch testCountDownLatch = new TestCountDownLatch();
        testCountDownLatch.downloadFiles();
    }


    /**
     * 使用多线程下载5张图片,并在所有文件下载完毕之后统计文件大小
     */
    private void downloadFiles() {
        CountDownLatch countDownLatch = new CountDownLatch(5);
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        List<Future<Long>> futures = new ArrayList<>();
        for (String targetUrl : targetUrls) {
            // submit和execute的区别:submit可以直接返回future结果,而execute必须将结果作为变量传入
            futures.add(executorService.submit(new DownLoadTask(countDownLatch, targetUrl)));
        }

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }

        long sum = 0;
        for (Future<Long> future : futures) {
            try {
                sum += future.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

        System.out.println("下载文件总大小:" + sum);

    }

    public static long downloadFile(String mUrl, String fileName) throws MalformedURLException {
        // 下载网络文件
        int byteSum = 0;
        int byteRead = 0;
        URL url = new URL(mUrl);
        try {
            URLConnection conn = url.openConnection();
            InputStream inStream = conn.getInputStream();
            FileOutputStream fs = new FileOutputStream("D:/" + fileName + ".png");
            byte[] buffer = new byte[1204];
            while ((byteRead = inStream.read(buffer)) != -1) {
                byteSum += byteRead;
                fs.write(buffer, 0, byteRead);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println(fileName + " size : " + byteSum);
        }
        return byteSum;
    }


    static class DownLoadTask implements Callable<Long> {

        private CountDownLatch countDownLatch;

        private String url;

        public DownLoadTask(CountDownLatch countDownLatch, String url) {
            this.countDownLatch = countDownLatch;
            this.url = url;
        }

        @Override
        public Long call() throws Exception {
          long sum = downloadFile(url, Thread.currentThread().getName());
          countDownLatch.countDown();
          return sum;
        }
    }

执行结果:

pool-1-thread-3 size : 34689
pool-1-thread-2 size : 34711
pool-1-thread-1 size : 34694
pool-1-thread-4 size : 34693
pool-1-thread-5 size : 34690
下载文件总大小:173477

代码2:

    /**
     * 开启5个线程下载文件,但必须同一时间开始。
     * 准点抢购等场景
     */  
      private void downloadFilesSameTime() {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (String url : targetUrls) {
            executorService.execute(new DownLoadTaskSameTime(countDownLatch, url));
        }

        System.out.println("开始下载");
        countDownLatch.countDown();
    }


    static class DownLoadTaskSameTime implements Runnable {

        private String url;

        private CountDownLatch countDownLatch;

       public DownLoadTaskSameTime(CountDownLatch countDownLatch, String url) {
           this.countDownLatch = countDownLatch;
           this.url = url;
       }

        @Override
        public void run() {
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                String treadName = Thread.currentThread().getName();
                System.out.println(treadName + "开始下载");
                downloadFile(url, treadName);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
        }
    }

执行结果:

开始下载
pool-1-thread-1开始下载
pool-1-thread-5开始下载
pool-1-thread-4开始下载
pool-1-thread-2开始下载
pool-1-thread-3开始下载
pool-1-thread-4 size : 34693
pool-1-thread-5 size : 34690
pool-1-thread-1 size : 34694
pool-1-thread-3 size : 34689
pool-1-thread-2 size : 34711

CyclicBarrier

作用:计数器

应用场景:cyclic是循环,barrier是栅栏,合起来就是可循环的栅栏。多线程执行任务,必须等到所有线程到达某个点之后,线程才可以继续执行。举例:军队中军人集合执行任务。每个人相当于一个线程,所有人必须全部到达集合点之后,才可以执行任务。

代码:

    public static void main(String[] args) {
        TestCyclicBarrier testCyclicBarrier = new TestCyclicBarrier();
        testCyclicBarrier.testCyclicBarrier();
    }

    private void testCyclicBarrier() {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                System.out.println("所有线程文件都下载结束了,各自展示文件大小吧");
            }
        });
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        for (String targetUrl : TestCountDownLatch.targetUrls) {
            // submit和execute的区别:submit可以直接返回future结果,而execute必须将结果作为变量传入
            executorService.submit(new DownloadFileTask(cyclicBarrier, targetUrl));
        }
    }


   static class DownloadFileTask implements Runnable {

        private CyclicBarrier cyclicBarrier;

        private String url;

        public DownloadFileTask(CyclicBarrier cyclicBarrier, String url) {
            this.cyclicBarrier = cyclicBarrier;
            this.url = url;
        }

        @Override
        public void run() {
            long sum = 0;
            try {
                sum = TestCountDownLatch.downloadFile(url, Thread.currentThread().getName());
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
            try {
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }

            System.out.println(sum);
        }
    }

执行结果:

pool-1-thread-1 size : 34694
pool-1-thread-3 size : 34689
pool-1-thread-5 size : 34690
pool-1-thread-2 size : 34711
pool-1-thread-4 size : 34693
所有线程文件都下载结束了,各自展示文件大小吧
34693
34694
34689
34690
34711

CyclicBarrier和CountDownLatch的区别

两者都可以作为多线程下的计数器,应用场景有一些小区别,将创建CountDownLatch的线程设为主线程的话,可以在主线程汇总其他线程返回的数据,或者控制其他线程同时执行;CyclicBarrier作用都是在其他线程,可以控制所有线程在某个点等待其他线程全部执行到该点后,所有线程继续执行。

此外CountDownLatch是不可重复使用的,而CyclicBarrier是可以重复使用的,调用reset()即可。

两者都是递减计数的。

Semaphore

作用:信号量,控制同时访问资源的数量

应用场景:应用在资源有明确访问数量的场景。停车场有固定数量的车位,在进入一辆车之后,车位数减1,出去一辆车,数量加1,当车位满了之后,车辆禁止进入;固定线程数的线程池,任何时候最多只有固定数量的线程在执行任务。

代码:

    public static void main(String[] args) {
        TestSemaphore testSemaphore = new TestSemaphore();
        testSemaphore.downloadFilesWithSemaphore();
    }


    /**
     * 仍然是下载五张图片,虽然线程池数量是5
     * 但是我希望同时最多三个文件在下载
     */
    private void downloadFilesWithSemaphore() {
        Semaphore semaphore = new Semaphore(3, true);
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (String url : TestCountDownLatch.targetUrls) {
            executorService.execute(new DownloadFileTask(semaphore, url));
        }
    }



    static class DownloadFileTask implements Runnable {

        private Semaphore semaphore;

        private String url;

        public DownloadFileTask(Semaphore semaphore, String url) {
            this.semaphore = semaphore;
            this.url = url;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                String threadName = Thread.currentThread().getName();
                System.out.println(threadName + "开始下载");
                TestCountDownLatch.downloadFile(url, threadName);
                System.out.println(threadName + "完成下载");
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
            semaphore.release();
        }
    }

执行结果:

pool-1-thread-1开始下载
pool-1-thread-2开始下载
pool-1-thread-5开始下载
pool-1-thread-2 size : 34711
pool-1-thread-5 size : 34690
pool-1-thread-5完成下载
pool-1-thread-1 size : 34694
pool-1-thread-1完成下载
pool-1-thread-2完成下载
pool-1-thread-3开始下载
pool-1-thread-4开始下载
pool-1-thread-3 size : 34689
pool-1-thread-3完成下载
pool-1-thread-4 size : 34693
pool-1-thread-4完成下载

总结

CountDownLatch:1. 主线程调用await()等待,所有子线程执行完成后调用countdown()后,计数器到0之后回调,做统计汇总;2. 所有子线程调用await()等待,主线程调用countdown()方法,子线程全部开始执行任务。

CyclicBarrier:所有子线程有两个任务A和B需要完成,所有子线程执行B任务前必须等待所有子线程都完成A任务。子线程执行A任务后,调用await(),先完成A任务的线程需等待其他线程完成A任务,全部完成后回调,所有子线程可继续执行B任务。CyclicBarrier可重置后继续使用,CountdownLatch不可以。

Semaphore:针对一个共享资源,控制访问其的线程数量(类似于停车场车位是共享资源,控制进入停车场的数量)。支持公平锁和非公平锁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值