java.util.concurrent中的常用组件
一. 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分钟后才可交卷,当时间到了,或学生都交完卷了考试结束。
这个场景中几个点需要注意:
- 考试时间为120分钟,30分钟后才可交卷,初始化考生完成试卷时间最小应为30分钟
- 对于能够在120分钟内交卷的考生,如何实现这些考生交卷
- 对于120分钟内没有完成考试的考生,在120分钟考试时间到后需要让他们强制交卷
- 在所有的考生都交完卷后,需要将控制线程关闭
实现思想:用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: }