1 CountDownLatch
倒计时门闩:[ˈkaʊntdaʊn] [lætʃ],顾名思义,门闩有多个,当门闩全部开启时,门闩数减为0,才能把门打开,在门前等待的人才能出去。
来个例子:在线程池中,并发执行10个任务,主线程观察任务执行情况,并等待任务全部执行结束,输出执行结果。
// 创建线程池,核心池-0,最大-10,timeout-0,队列-实时队列
// 当有任务来的时候,会立即创建线程处理,如果任务结束,线程立即销毁
ThreadPoolExecutor executor = new ThreadPoolExecutor(0, 10, 0L, TimeUnit.SECONDS, new SynchronousQueue<>());
// 初始化CountDownLatch,大小为10
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
// 往线程池提交任务
executor.execute(new Runnable() {
@Override
public void run() {
try {
// 模拟耗时
Thread.sleep(Long.valueOf(String.valueOf(new Double(Math.random() * 1000).intValue())));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("over");
// 子线程完成,countDownLatch执行countDown
countDownLatch.countDown();
}
});
// 打印线程池运行状态
System.out.println(
"正在执行的任务数:" + executor.getPoolSize() + ",已执行结束的任务数:" + executor.getCompletedTaskCount());
}
//观测5次
int n = 5;
while (n > 0) {
try {
TimeUnit.MILLISECONDS.sleep(60);
} catch (InterruptedException e) {
e.printStackTrace();
}
n--;
System.out.println(
"观测:正在执行的任务数:" + executor.getPoolSize() + ",已执行结束的任务数:" + executor.getCompletedTaskCount());
}
// 阻塞当前线程,直到所有子线程都执行countDown方法才会继续执行
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印线程池运行状态
System.out.println("最终结果:正在执行的任务数:" + executor.getPoolSize() + ",已执行结束的任务数:" + executor.getCompletedTaskCount());
// 停止运行线程池
executor.shutdown();
结果:
- 第一阶段:任务队列依次添加到线程池中
- 第二阶段:任务依次执行结束,观测期间执行完成4个任务
- 第三阶段:主线程等待所有子线程执行结束,await等待,当所有countDown都运行完成,主线程输出最终结果
正在执行的任务数:1,已执行结束的任务数:0
正在执行的任务数:2,已执行结束的任务数:0
正在执行的任务数:3,已执行结束的任务数:0
正在执行的任务数:4,已执行结束的任务数:0
正在执行的任务数:5,已执行结束的任务数:0
正在执行的任务数:6,已执行结束的任务数:0
正在执行的任务数:7,已执行结束的任务数:0
正在执行的任务数:8,已执行结束的任务数:0
正在执行的任务数:9,已执行结束的任务数:0
正在执行的任务数:10,已执行结束的任务数:0
over
over
观测:正在执行的任务数:8,已执行结束的任务数:2
观测:正在执行的任务数:8,已执行结束的任务数:2
观测:正在执行的任务数:8,已执行结束的任务数:2
over
观测:正在执行的任务数:7,已执行结束的任务数:3
over
观测:正在执行的任务数:6,已执行结束的任务数:4
over
over
over
over
over
over
最终结果:正在执行的任务数:0,已执行结束的任务数:10
注意点:
CountDownLatch在对任务执行完成,进行判断时,要注意异常考虑,确保所有线程都可以执行countDown,即确保CountDownLatch可以归0,不然可能造成await线程无限期阻塞,如果无法确保,则await线程采用超时等待机制,如:await(long timeout, TimeUnit unit)。
2 CyclicBarrier
循环障碍:['saiklik]['bæriər],顾名思义,障碍只有一个,当障碍前人数达到一定量才会打开,且障碍可以循环使用,打开之后,放走一波人,立即关上,等待第二次使用。
来个例子:一群好朋友去徒步旅行,等人到齐了,一起出发,中途大家进行休息,等人全休息好了,再重新启程。
int total = 5;
// 创建线程池,核心池-0,最大-5,timeout-0,队列-实时队列
// 当有任务来的时候,会立即创建线程处理,如果任务结束,线程立即销毁
ThreadPoolExecutor executor = new ThreadPoolExecutor(0, total, 0L, TimeUnit.SECONDS, new SynchronousQueue<>());
// 初始化CyclicBarrier,大小为5
CyclicBarrier cyclicBarrier = new CyclicBarrier(total);
for (int i = 0; i < total; i++) {
// 往线程池提交任务
executor.execute(new Runnable() {
@Override
public void run() {
try {
// 模拟随机等待
Thread.sleep(Long.valueOf(String.valueOf(new Double(Math.random() * 1000).intValue())));
System.out.println("Waiting for everyone to be ready,left number:"+(total-cyclicBarrier.getNumberWaiting()));
// 等待队友就位
cyclicBarrier.await();
System.out.println("Lat's go");
// 模拟随机等待
Thread.sleep(Long.valueOf(String.valueOf(new Double(Math.random() * 1000).intValue())));
System.out.println("Waiting for everyone to have a good rest,left number:"+(total-cyclicBarrier.getNumberWaiting()));
// 等待队友休息结束
cyclicBarrier.await();
System.out.println("Lat's go again");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
});
}
// 停止运行线程池
executor.shutdown();
结果:一目了然!
Waiting for everyone to be ready,left number:5
Waiting for everyone to be ready,left number:4
Waiting for everyone to be ready,left number:3
Waiting for everyone to be ready,left number:2
Waiting for everyone to be ready,left number:1
Lat's go
Lat's go
Lat's go
Lat's go
Lat's go
Waiting for everyone to have a good rest,left number:5
Waiting for everyone to have a good rest,left number:4
Waiting for everyone to have a good rest,left number:3
Waiting for everyone to have a good rest,left number:2
Waiting for everyone to have a good rest,left number:1
Lat's go again
Lat's go again
Lat's go again
Lat's go again
Lat's go again
其他知识点介绍:
- await(timeout,TimeUnit):当等待超时,会抛出TimeoutException异常,并停止等待
- isBroken():获取是否破损标志位broken的值:CyclicBarrier初始化时或调用CyclicBarrier.reset()方法后,则broken=false;如果等待线程被中断或超时,则broken=true
- reset():重置CyclicBarrier,是否破损标志位broken置为false,之前等待的,会抛出BrokenBarrierException异常
- BrokenBarrierException:当某等待线程(A)被中断,或等待超时,或线程(A)调用CyclicBarrier.reset(),那么除线程(A)之外的其他等待线程都会触发该异常
PS:CyclicBarrier与CountDownLatch,功能上其实可以相通,CyclicBarrier也就多了一个循环使用的概念。
3 Semaphore
信号量:['seməfɔːr],顾名思义,就是个信号!但是它有量!只有拿到信号的才能通过,不然会被阻拦,已经获取信号的可以释放,这样就可以把机会让给其他人。
public class SemaphoreTest {
public static void main(String[] args) {
int total = 4;
ThreadPoolExecutor executor = new ThreadPoolExecutor(0, total, 0L, TimeUnit.SECONDS, new SynchronousQueue<>());
//池子大小为2
final Pool pool = new Pool(2);
Runnable worker = new Runnable() {
public void run() {
try {
pool.in(1);//一人入场
// 模拟耗时
Thread.sleep(Long.valueOf(String.valueOf(new Double(Math.random() * 1000).intValue())));
pool.out(1);//一人出场
} catch (InterruptedException ex) {
}
}
};
for (int i = 0; i < total; i++) {
executor.submit(worker);
}
executor.shutdown();
}
}
/**
* 模拟资源池
*/
class Pool {
Semaphore pass = null;
public Pool(int size) {
pass = new Semaphore(size);
}
public void in(int num) throws InterruptedException {
// 获取信号量才能入场
System.out.println("Try to get a pass...,left:"+pass.availablePermits());
pass.acquire(num);
//入场
System.out.println("Got a pass,I'm in...");
}
public void out(int num) {
// 归还信号量,出场
System.out.println("Release a pass,I'm out...");
pass.release(num);
}
}
结果:先是两个人依次入场,入场券减少到0,之后入场的等待入场券,无法入场,当前两个退出后,后两个才入场成功
Try to get a pass...,left:2
Got a pass,I'm in...
Try to get a pass...,left:1
Got a pass,I'm in...
Try to get a pass...,left:0
Try to get a pass...,left:0
Release a pass,I'm out...
Got a pass,I'm in...
Release a pass,I'm out...
Got a pass,I'm in...
Release a pass,I'm out...
Release a pass,I'm out...
PS:Semaphore相当于允许固定并发访问的synchronized,而且每次入场消耗还可以控制,程序中为 1,也可以改为 2哦
爱家人,爱生活,爱设计,爱编程,拥抱精彩人生!