zoukankan      html  css  js  c++  java
  • Java的几个同步辅助类

    Java为我们提供了一些同步辅助类,利用这些辅助类我们可以在多线程编程中,灵活地把握线程的状态。

    CountDownLatch

    CountDownLatch一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

    再CountDownLatch中两个比较关键的方法:

    public void await() throws InterruptedException;
    public void countDown();
    

    CountDownLatch是一个计数器,它的构造方法中需要设置一个数值,用来设定计数的次数。每次调用countDown()方法之后,这个计数器都会减去1,CountDownLatch会一直阻塞着调用await()方法的线程,直到计数器的值变为0

    设想有这样一个功能需要Thread1、Thread2、Thread3、Thread4四条线程分别统计C、D、E、F四个盘的大小,所有线程都统计完毕交给主线程去做汇总,利用CountDownLatch来完成就非常轻松。

    public class CountDownLatchTest {
    
        private static CountDownLatch count = new CountDownLatch(4);
        private static ExecutorService service = Executors.newFixedThreadPool(6);
    
        public static void main(String args[]) throws InterruptedException {
    
            for (int i = 0; i < 4; i++) {
                service.execute(() -> {
                    // 模拟任务耗时
                    try {
                        int timer = new Random().nextInt(5);
                        TimeUnit.SECONDS.sleep(timer);
                        System.out.printf("%s时完成磁盘的统计任务,耗费%d秒.
    ", new Date().toString(), timer);
                        // 任务完成之后,计数器减一
                        count.countDown();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
            // 主线程一直被阻塞,知道count的计数器被设置为0
            count.await();
    
            System.out.printf("%s时全部任务都完成,执行合并计算.
    ", new Date().toString());
            service.shutdown();
        }
    }
    

    CyclicBarrier

    Barrier在英语中是屏障的意思,这个同步工具会阻塞调用的线程,直到条件满足时,阻塞的线程同时被打开。

    public int await() throws InterruptedException, BrokenBarrierException
    

    CyclicBarrier初始化的时候,设置一个屏障数。线程调用await()方法的时候,这个线程就会被阻塞,当调用await()的线程数量到达屏障数的时候,主线程就会取消所有被阻塞线程的状态。

    CyclicBarrier的构造方法中,还可以设置一个barrierAction

    在所有的屏障都到达之后,会启动一个线程来运行这里面的代码。这里举一个例子:百米赛跑的运动员起跑前需要准备,所有选手准备完毕之后,才可以同时起跑。

    public class CyclicBarrierTest {
    
        private static CyclicBarrier cyclicBarrier = new CyclicBarrier(8);
        private static ExecutorService service = Executors.newFixedThreadPool(50);
    
        public static void main(String args[]) {
            for (int i = 1; i < 9; i++) {
                service.execute(new Thread(new Runner(i, cyclicBarrier)));
            }
            service.shutdown();
        }
    }
    // 运动员类
    public class Runner implements Runnable {
    
        private int number;
        private CyclicBarrier cyclicBarrier;
    
        public Runner(int number, CyclicBarrier cyclicBarrier) {
            this.number = number;
            this.cyclicBarrier = cyclicBarrier;
        }
    
        @Override
        public void run() {
            try {
                int timer = new Random().nextInt(5);
                TimeUnit.SECONDS.sleep(timer);
                System.out.printf("%d号选手准备完毕,准备时间%d
    ", number, timer);
                cyclicBarrier.await();
                System.out.printf("%d号选手于%s时起跑!
    ", number, new Date().toString());
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
    

    输出:

    1号选手准备完毕,准备时间0
    4号选手准备完毕,准备时间0
    5号选手准备完毕,准备时间1
    8号选手准备完毕,准备时间1
    3号选手准备完毕,准备时间2
    2号选手准备完毕,准备时间3
    7号选手准备完毕,准备时间3
    6号选手准备完毕,准备时间3
    7号选手于Sun Mar 27 21:19:00 CST 2016时起跑!
    2号选手于Sun Mar 27 21:19:00 CST 2016时起跑!
    5号选手于Sun Mar 27 21:19:00 CST 2016时起跑!
    6号选手于Sun Mar 27 21:19:00 CST 2016时起跑!
    3号选手于Sun Mar 27 21:19:00 CST 2016时起跑!
    8号选手于Sun Mar 27 21:19:00 CST 2016时起跑!
    4号选手于Sun Mar 27 21:19:00 CST 2016时起跑!
    1号选手于Sun Mar 27 21:19:00 CST 2016时起跑!
    

    相比CountDownLatchCyclicBarrier是可以被循环使用的,而且遇到线程中断等情况时,还可以利用reset()方法,重置计数器,从这些方面来说,CyclicBarrier会比CountDownLatch更加灵活一些。

    Semaphore

    Semaphore被用于控制特定资源在同一个时间被访问的个数。类似连接池的概念,保证资源可以被合理的使用。

    Semaphore的几个重要方法:

    // 获取资源
    public void acquire() throws InterruptedException
    // 释放资源
    public void release()
    

    Semaphore的构造方法可以设置一个int值来设置一个计数器,用于表示资源同时可以被多少外部环境使用。每使用一次acquire(),计数器都会去减去一,而每次调用release()计数器则会增加一。当计数器的值为0的时候,外部的环境被阻塞,直到Semaphore有空闲的资源可以被使用。

    public class SemaphoreTest {
    
        private static Semaphore semaphore = new Semaphore(3);
        private static ExecutorService service = Executors.newFixedThreadPool(6);
    
        public static void main(String args[]) {
    
            // 执行9个任务
            for (int i = 0; i < 9; i++) {
                service.execute(() -> {
                    try {
                        semaphore.acquire();
                        System.out.printf("%s时获取资源,并调用.
    ", new Date().toString());
                        // 线程挂起3秒
                        TimeUnit.SECONDS.sleep(3);
                        semaphore.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
            service.shutdown();
        }
    }
    

    运行的结果就是:

    Sun Mar 27 20:18:16 CST 2016时获取资源,并调用.
    Sun Mar 27 20:18:16 CST 2016时获取资源,并调用.
    Sun Mar 27 20:18:16 CST 2016时获取资源,并调用.
    Sun Mar 27 20:18:19 CST 2016时获取资源,并调用.
    Sun Mar 27 20:18:19 CST 2016时获取资源,并调用.
    Sun Mar 27 20:18:19 CST 2016时获取资源,并调用.
    Sun Mar 27 20:18:22 CST 2016时获取资源,并调用.
    Sun Mar 27 20:18:22 CST 2016时获取资源,并调用.
    Sun Mar 27 20:18:22 CST 2016时获取资源,并调用.
    

    虽然线程池允许6个最大线程数量,但是同一个时间内只用三个任务被执行。

  • 相关阅读:
    课后作业03
    课堂测验02
    构建之法阅读笔记02
    进度条 二
    软件工程个人作业02
    Sprint6
    Sprint5
    Alpha版总结会议
    Beta版总结会议
    Alpha阶段项目Postmortem会议总结
  • 原文地址:https://www.cnblogs.com/whthomas/p/java_concurrent_tools.html
Copyright © 2011-2022 走看看