java.util.concurrent中的常用组件

北战南征 提交于 2020-03-13 01:59:28

一. CountDownLatch

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

CountDownLatch的一个非常典型的应用场景是:有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。假如我们这个想要继续往下执行的任务调用一个CountDownLatch对象的await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch对象上的countDown()方法,这个调用await()方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到0为止。

CountDownLatch的主要方法:

                            方法名称                                       方法作用
public CountDownLatch(int count) 构造器参数指定了计数的次数,即有几个独立的任务
public void CountDown() 当前线程调用此方法,计数减一,即其中一个任务完成
public void await() 当前线程调用此方法会一直被阻塞,直到计数为0,即所有子任务都完成了。

 

   1: package com.wbl.test.thread.countDownLatch;
   2:  
   3: import java.text.SimpleDateFormat;
   4: import java.util.Date;
   5: import java.util.concurrent.CountDownLatch;
   6:  
   7: /**
   8:  * Created with Simple_love
   9:  * Date: 2016/3/19.
  10:  * Time: 16:06
  11:  */
  12: public class CountDownLatchDemo {
  13:     final static SimpleDateFormat                         sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  14:  
  15:         public static void main(String[] args) throws InterruptedException {
  16:                 CountDownLatch countDownLatch = new CountDownLatch(2);    //两个工人协同工作
  17:                 Worker worker1 = new Worker("work1",5000,countDownLatch);
  18:                 Worker worker2 = new Worker("work2",8000,countDownLatch);
  19:                 worker1.start();
  20:                 worker2.start();
  21:                 countDownLatch.await();   //等待所有的工人结束
  22:                 System.out.println("all work done at " + sdf.format(new Date()));
  23:         }
  24:  
  25:         static class Worker extends Thread{
  26:                 String name;
  27:                 int workTime;
  28:                 CountDownLatch countDownLatch;
  29:                 public Worker(String name,int workTime,CountDownLatch countDownLatch){
  30:                         this.name = name;
  31:                         this.workTime = workTime;
  32:                         this.countDownLatch = countDownLatch;
  33:                 }
  34:  
  35:                 public void doWork(){
  36:                         try {
  37:                                 Thread.sleep(workTime);
  38:                         }catch (InterruptedException e){
  39:                                 e.printStackTrace();
  40:                         }
  41:                 }
  42:  
  43:                 @Override
  44:                 public void run() {
  45:                         System.out.println("Worker " + name + " do work begin at " + sdf.format(new Date()));
  46:                         doWork();//工作了
  47:                        System.out.println("Worker "+name+" do work complete at "+sdf.format(new Date()));
  48:                        countDownLatch.countDown();//工人完成工作,计数器减一
  49:  
  50:                 }
  51:         }
  52: }

 

CountDownLatch被设计为只触发一次,计数值不能被重置。一旦计数器的值初始后,唯一可以修改它的方法就是之前用的 countDown() 方法。当计数器到达0时, 全部调用 await() 方法会立刻返回,接下来任何countDown() 方法的调用都将不会造成任何影响。

二. CyclicBarrier

字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。

CyclicBarrier的主要方法

                            方法名称                                       方法作用
public CyclicBarrier(int): 构造器参数指定线程的个数,即有几个互相等待的任务
public void CyclicBarrier(int,Runnable):   当一组的线程全都到达某个状态后,开始执行Runnable对象
public void await() 某个线程到达barrier状态

 

CyclicBarrier的应用场景:在某种需求中,比如一个大型的任务,常常需要分配好多子任务去执行,只有当所有子任务都执行完成时候,才能执行主任务,这时候,就可以选择CyclicBarrier了。

实例分析:我们需要统计全国的业务数据。其中各省的数据库是独立的,也就是说按省分库。并且统计的数据量很大,统计过程也比较慢。为了提高性能,快速计算。我们采取并发的方式,多个线程同时计算各省数据,最后再汇总统计。在这里CyclicBarrier就非常有用。

   1: /**  
   2: * 各省数据独立,分库存偖。为了提高计算性能,统计时采用每个省开一个线程先计算单省结果,最后汇总。  
   3: *   
   4: * @author guangbo email:weigbo@163.com  
   5: *   
   6: */  
   7: public class Total {   
   8:   
   9:    // private ConcurrentHashMap result = new ConcurrentHashMap();   
  10:   
  11:    public static void main(String[] args) {   
  12:        TotalService totalService = new TotalServiceImpl();   
  13:         CyclicBarrier barrier = new CyclicBarrier(5,   
  14:                 new TotalTask(totalService));   
  15:   
  16:         // 实际系统是查出所有省编码code的列表,然后循环,每个code生成一个线程。   
  17:         new BillTask(new BillServiceImpl(), barrier, "北京").start();   
  18:         new BillTask(new BillServiceImpl(), barrier, "上海").start();   
  19:         new BillTask(new BillServiceImpl(), barrier, "广西").start();   
  20:         new BillTask(new BillServiceImpl(), barrier, "四川").start();   
  21:         new BillTask(new BillServiceImpl(), barrier, "黑龙江").start();    
  22:    }   
  23: }   
  24:   
  25: /**  
  26:  * 主任务:汇总任务  
  27:  */  
  28: class TotalTask implements Runnable {   
  29:     private TotalService totalService;   
  30:   
  31:     TotalTask(TotalService totalService) {   
  32:         this.totalService = totalService;   
  33:     }   
  34:   
  35:     public void run() {   
  36:         // 读取内存中各省的数据汇总,过程略。   
  37:         totalService.count();   
  38:         System.out.println("=======================================");   
  39:         System.out.println("开始全国汇总");   
  40:     }   
  41: }   
  42:   
  43: /**  
  44:  * 子任务:计费任务  
  45:  */  
  46: class BillTask extends Thread {   
  47:     // 计费服务   
  48:     private BillService billService;   
  49:     private CyclicBarrier barrier;   
  50:     // 代码,按省代码分类,各省数据库独立。   
  51:     private String code;   
  52:   
  53:     BillTask(BillService billService, CyclicBarrier barrier, String code) {   
  54:         this.billService = billService;   
  55:        this.barrier = barrier;   
  56:         this.code = code;   
  57:     }   
  58:   
  59:     public void run() {   
  60:         System.out.println("开始计算--" + code + "省--数据!");   
  61:         billService.bill(code);   
  62:         // 把bill方法结果存入内存,如ConcurrentHashMap,vector等,代码略   
  63:         System.out.println(code + "省已经计算完成,并通知汇总Service!");   
  64:         try {   
  65:             // 通知barrier已经完成   
  66:             barrier.await();   
  67:         } catch (InterruptedException e) {   
  68:             e.printStackTrace();   
  69:         } catch (BrokenBarrierException e) {   
  70:             e.printStackTrace();   
  71:         }   
  72:     }   
  73:   
  74: }  

 

CountDownLatch和CyclicBarrier的区别

CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。

三. DelayQueue

DelayQueue类的主要作用:是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。注意:不能将null元素放置到这种队列中。

Delayed,一种混合风格的接口,用来标记那些应该在给定延迟时间之后执行的对象。此接口的实现必须定义一个 compareTo 方法,该方法提供与此接口的 getDelay 方法一致的排序。

(1) 实例场景

模拟一个考试的日子,考试时间为120分钟,30分钟后才可交卷,当时间到了,或学生都交完卷了考试结束。

这个场景中几个点需要注意:

  1. 考试时间为120分钟,30分钟后才可交卷,初始化考生完成试卷时间最小应为30分钟
  2. 对于能够在120分钟内交卷的考生,如何实现这些考生交卷
  3. 对于120分钟内没有完成考试的考生,在120分钟考试时间到后需要让他们强制交卷
  4. 在所有的考生都交完卷后,需要将控制线程关闭

实现思想:用DelayQueue存储考生(Student类),每一个考生都有自己的名字和完成试卷的时间,Teacher线程对DelayQueue进行监控,收取完成试卷小于120分钟的学生的试卷。当考试时间120分钟到时,先关闭Teacher线程,然后强制DelayQueue中还存在的考生交卷。每一个考生交卷都会进行一次countDownLatch.countDown(),当countDownLatch.await()不再阻塞说明所有考生都交完卷了,而后结束考试。

   1: package com.wbl.test.thread.delayQueue;
   2:  
   3: import java.util.Iterator;
   4: import java.util.Random;
   5: import java.util.concurrent.CountDownLatch;
   6: import java.util.concurrent.DelayQueue;
   7: import java.util.concurrent.Delayed;
   8: import java.util.concurrent.TimeUnit;
   9:  
  10: /**
  11:  * Created with Simple_love
  12:  * Date: 2016/3/19.
  13:  * Time: 14:29
  14:  */
  15: public class Exam {
  16:         public static void main(String[] args) throws InterruptedException {
  17:                 int count = 20;
  18:                 DelayQueue<Student> students = new DelayQueue<>();
  19:                 Random random = new Random();
  20:                 CountDownLatch countDownLatch = new CountDownLatch(count+1);
  21:                 for (int i = 1; i <= 20; i++)
  22:                         students.add(new Student("student" + i,30+random.nextInt(120),countDownLatch));
  23:                 Thread teacherThread = new Thread(new Teacher(students));
  24:                 students.add(new EndExam(students,120,countDownLatch,teacherThread));
  25:                 teacherThread.start();
  26:                 countDownLatch.await();
  27:                 System.out.println(" 考试时间到,全部交卷!");
  28:         }
  29: }
  30: class Student implements Runnable,Delayed{
  31:         private String name;
  32:         private long workTime; //考试时间
  33:         private long submitTime; //交卷时间
  34:         private boolean isForce = false;
  35:         private CountDownLatch countDownLatch;
  36:  
  37:         public Student() {
  38:         }
  39:  
  40:         public Student(String name, long workTime,CountDownLatch countDownLatch) {
  41:                 this.name = name;
  42:                 this.workTime = workTime;
  43:                 this.submitTime = TimeUnit.NANOSECONDS.convert(workTime,TimeUnit.NANOSECONDS) + System.nanoTime();
  44:                 this.countDownLatch = countDownLatch;
  45:         }
  46:  
  47:         @Override
  48:         public long getDelay(TimeUnit unit) {
  49:                 return unit.convert(submitTime-System.nanoTime(),TimeUnit.NANOSECONDS);
  50:         }
  51:  
  52:         @Override
  53:         public int compareTo(Delayed o) {
  54:                 if (o == null || ! (o instanceof Student))
  55:                         return 1;
  56:                 if (o == this)
  57:                         return 0;
  58:                 Student temp = (Student)o;
  59:                 if (this.workTime > temp.workTime)
  60:                         return 1;
  61:                 else if (this.workTime == temp.workTime)
  62:                         return 0;
  63:                 else
  64:                         return -1;
  65:         }
  66:  
  67:         @Override
  68:         public void run() {
  69:                 if (isForce) {
  70:                         System.out.println(name + " 交卷, 希望用时" + workTime + "分钟"+" ,实际用时 120分钟" );
  71:                 }else {
  72:                         System.out.println(name + " 交卷, 希望用时" + workTime + "分钟"+" ,实际用时 "+workTime +" 分钟");
  73:                 }
  74:                 countDownLatch.countDown();
  75:         }
  76:  
  77:         public boolean isForce() {
  78:                 return isForce;
  79:         }
  80:  
  81:         public void setForce(boolean isForce) {
  82:                 this.isForce = isForce;
  83:         }
  84: }
  85:  
  86: class Teacher implements Runnable{
  87:  
  88:         private DelayQueue<Student> students;
  89:  
  90:         public Teacher(DelayQueue<Student> students) {
  91:                 this.students = students;
  92:         }
  93:  
  94:         @Override
  95:         public void run() {
  96:                 try {
  97:                         System.out.println("exam start");
  98:                         while (!Thread.interrupted())
  99:                                 students.take().run();
 100:                 }catch (InterruptedException e){
 101:                         System.out.println("exam end");
 102:                 }
 103:         }
 104: }
 105:  
 106: class EndExam extends Student{
 107:         private DelayQueue<Student> students;
 108:         private CountDownLatch countDownLatch;
 109:         private Thread teacher;
 110:  
 111:         public EndExam(DelayQueue<Student> students,long workTime,CountDownLatch countDownLatch,Thread teacher){
 112:                 super("强制收卷",workTime,countDownLatch);
 113:                 this.students = students;
 114:                 this.countDownLatch = countDownLatch;
 115:                 this.teacher = teacher;
 116:         }
 117:  
 118:         @Override
 119:         public void run() {
 120:                 teacher.interrupt();
 121:                 System.out.println("强制收卷");
 122:                 Student temp;
 123:                 for (Iterator<Student> iterator = students.iterator();iterator.hasNext();){
 124:                         temp = iterator.next();
 125:                         temp.setForce(true);
 126:                         temp.run();
 127:                 }
 128:                 countDownLatch.countDown();
 129:         }
 130: }

 

四. Semaphore

Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。

Semaphore实现的功能就类似厕所有5个坑,假如有10个人要上厕所,那么同时只能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中 的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了。另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。

   1: public class SemaphoreTest {
   2:  
   3:      public static void main(String[] args) {  
   4:         // 线程池 
   5:         ExecutorService exec = Executors.newCachedThreadPool();  
   6:         // 只能5个线程同时访问 
   7:         final Semaphore semp = new Semaphore(5);  
   8:         // 模拟20个客户端访问 
   9:         for (int index = 0; index < 20; index++) {
  10:             final int NO = index;  
  11:             Runnable run = new Runnable() {  
  12:                 public void run() {  
  13:                     try {  
  14:                         // 获取许可 
  15:                         semp.acquire();  
  16:                         System.out.println("Accessing: " + NO);  
  17:                         Thread.sleep((long) (Math.random() * 10000));  
  18:                         // 访问完后,释放 ,如果屏蔽下面的语句,则在控制台只能打印5条记录,之后线程一直阻塞
  19:                         semp.release();  
  20:                     } catch (InterruptedException e) {  
  21:                     }  
  22:                 }  
  23:             };  
  24:             exec.execute(run);  
  25:         }  
  26:         // 退出线程池 
  27:         exec.shutdown();  
  28:     }  
  29: }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!