jdk1.8
上一章节主要讲述如何按照fork-join范式如何将大任务划分成多个小任务分而治之;
从本章节主要讲述两个工具类CountDownLatch和CyclicBarrier使用及比较;
CountDownLatch:一种同步辅助工具,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成;CountDownLatch使用给定的计数初始化。wait方法阻塞,直到当前计数由于countDown()方法的调用而达到零,在此之后释放所有等待的线程,并立即返回任何后续的wait调用。这是一个一次性现象——计数无法重置。
CyclicBarrier:一种同步辅助工具,它允许一组线程相互等待到达共同的屏障点。循环屏障在涉及固定大小的线程组的程序中非常有用,这些线程组偶尔必须相互等待。这个屏障被称为循环的,因为它可以在等待的线程释放后被重用。
CountDownLatch:一种同步辅助工具,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成;
CountDownLatch使用给定的计数初始化。wait方法阻塞,直到当前计数由于countDown()方法的调用而达到零,在此之后释放所有等待的线程,并立即返回任何后续的wait调用。这是一个一次性现象——计数无法重置。如果您需要一个可以重置计数的版本,可以考虑使用cycle barrier。
CountDownLatch是一种通用的同步工具,可以用于多种目的。其中经典的两种使用方式有:
方式一实践:
由于这种方式很贴近运动员赛跑,需等待所有运动员准备完成之后才统一起跑,所以这种方式CountDownLatch也被称作发令枪;这个调用方式也常用于模拟高并发,大家可以参考之前的文章高并发编程之高并发场景:秒杀(无锁、排他锁、乐观锁、redis缓存的逐步演变)
package pers.cc.curriculum2.countDownLatch;
import java.util.concurrent.CountDownLatch;
/**
* CountDownLatch测试类:10个运动员一一准备,等都准备好统一听发令枪一起起跑
*
*
* @author cc
*
*/
public class CountDownLatchAwaitN {
// 定义一个总数为14的计数器
static CountDownLatch latch = new CountDownLatch(14);
private static class InitThread implements Runnable {
private String name;
public InitThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + "已准备好!");
// 初始化线程完成工作了,调用countDown方法扣减一次;
latch.countDown();
try {
// 堵塞所有子进程等候计数器号令
latch.await();
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "启动:" + System.currentTimeMillis());
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i <= 10; i++) {
Thread thread = new Thread(new InitThread("运动员" + i));
thread.start();
}
Thread.sleep(1000);
System.out.println("3");
latch.countDown();
System.out.println("2");
latch.countDown();
System.out.println("1");
latch.countDown();
System.out.println("啪");
latch.countDown();
}
}
运行结果如下,可以看到子线程被await方法堵塞,知道计算器减到0统一启动:
运动员1已准备好!
运动员2已准备好!
运动员6已准备好!
运动员10已准备好!
运动员3已准备好!
运动员4已准备好!
运动员7已准备好!
运动员5已准备好!
运动员8已准备好!
运动员9已准备好!
3
2
1
啪
运动员1启动:1549087623847
运动员6启动:1549087623847
运动员4启动:1549087623847
运动员2启动:1549087623847
运动员3启动:1549087623847
运动员9启动:1549087623847
运动员10启动:1549087623847
运动员8启动:1549087623847
运动员5启动:1549087623847
运动员7启动:1549087623847
方式二实践:
方式二就类似我们需要装货出发一样;n个人同时搬货,只有当我们车上装满了指定个数count个货物,才可以进行配送;
package pers.cc.curriculum2.countDownLatch;
import java.util.concurrent.CountDownLatch;
import pers.cc.tools.SleepTools;
/**
* CountDownLatch测试类:装满指定个数货物才能进行配送
*
*
* @author cc
*
*/
public class CountDownLatchAwaitOne {
// 定义一个总数为30的计数器
static CountDownLatch latch = new CountDownLatch(30);
private static class InitThread implements Runnable {
@Override
public void run() {
// 每个人装3个货物
for (int i = 1; i <= 3; i++) {
SleepTools.ms(200);
System.out.println("Thread_" + Thread.currentThread().getId()
+ "已准备好一个货物!");
// 初始化线程完成工作了,调用countDown方法扣减一次;
latch.countDown();
}
}
}
public static void main(String[] args) throws InterruptedException {
// 10个人装,每个人装3个货物
for (int i = 1; i <= 10; i++) {
Thread thread = new Thread(new InitThread());
thread.start();
}
latch.await();
System.out.println("货物装好出发");
}
}
上述的演示结果如下:
Thread_15已准备好一个货物!
.......
Thread_19已准备好一个货物!
货物装好出发
总结:
构造方法:
CountDownLatch(int count):构造使用给定的计数初始化的CountDownLatch。
常用方法:
CyclicBarrier:一种同步辅助工具,它允许一组线程相互等待到达共同的屏障点。循环屏障在涉及固定大小的线程组的程序中非常有用,这些线程组偶尔必须相互等待。这个屏障被称为循环的,因为它可以在等待的线程释放后被重用。
cycles barrier支持一个可选的Runnable命令,该命令在参与方的最后一个线程到达之后,但在任何线程被释放之前,在每个屏障点上运行一次。此屏障操作对于在任何缔约方继续之前更新共享状态非常有用。
cycles barrier适用的场景:
场景一:就是指定线程都进入指定等待状态后;统一进行后续的动作;
场景二:就是指定线程都进入指定等待状态后;先进行指定Runnable的操作后,再统一进行后续的动作;
package pers.cc.curriculum2.cyclesBarrier;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;
import pers.cc.tools.SleepTools;
/**
* CyclesBarrier:一组线程相互等待到达共同的屏障点,可选择先执行一个Runnable操作,也可立即进行线程的后续操作
*
* @author cc
*
*/
public class CyclesBarrierTest {
// 场景1:定义一组线程相互等待后立即执行后续
private static CyclicBarrier barrier1 = new CyclicBarrier(5);
// 场景2:定义一组线程相互等待后先执行CollectThread线程再执行各自后续
private static CyclicBarrier barrier2 = new CyclicBarrier(5,
new CollectThread());
// 存放子线程工作结果的容器
private static ConcurrentHashMap < String, Long > resultMap = new ConcurrentHashMap <>();
/**
* 场景1初始线程
*
* @author cc
*
*/
private static class InitThread1 implements Runnable {
@Override
public void run() {
long id = Thread.currentThread().getId();
try {
Thread.sleep(200 + id);
System.out.println(id + "....is await");
barrier1.await();
System.out.println("Thread_" + id + " ....do its business ");
}
catch (InterruptedException e) {
e.printStackTrace();
}
catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* 场景2初始线程
*
* @author cc
*
*/
private static class InitThread2 implements Runnable {
@Override
public void run() {
long id = Thread.currentThread().getId();
// 线程本身的处理结果
resultMap.put(Thread.currentThread().getId() + "", id);
// 随机决定工作线程的是否睡眠
Random r = new Random();
try {
if (r.nextBoolean()) {
Thread.sleep(2000 + id);
}
System.out.println(id + "....is await");
barrier2.await();
System.out.println("Thread_" + id + " ....do its business ");
}
catch (Exception e) {
e.printStackTrace();
}
}
}
// 负责屏障开放以后的工作
private static class CollectThread implements Runnable {
@Override
public void run() {
StringBuilder result = new StringBuilder();
for (Map.Entry < String, Long > workResult : resultMap.entrySet()) {
result.append("[" + workResult.getValue() + "]");
}
System.out.println(" the result = " + result);
System.out.println("do other business........");
}
}
public static void main(String[] args) {
System.out
.println("——————————————————————场景1——————————————————————————");
for (int i = 0; i < 5; i++) {
new Thread(new InitThread1()).start();
}
// 休眠两秒
SleepTools.second(2);
System.out
.println("——————————————————————场景2——————————————————————————");
for (int i = 0; i < 5; i++) {
new Thread(new InitThread2()).start();
}
}
}
运行结果如下:
——————————————————————场景1——————————————————————————
10....is await
11....is await
12....is await
13....is await
14....is await
Thread_10 ....do its business
Thread_11 ....do its business
Thread_12 ....do its business
Thread_13 ....do its business
Thread_14 ....do its business
——————————————————————场景2——————————————————————————
16....is await
17....is await
15....is await
18....is await
19....is await
the result = [15][16][17][18][19]
do other business........
Thread_19 ....do its business
Thread_16 ....do its business
Thread_18 ....do its business
Thread_15 ....do its business
Thread_17 ....do its business
总结:
构造方法:
CyclicBarrier(int parties):创建一个新的cycle barrier,当给定数量的参与方(线程)等待它时,它将跳闸即消除屏障,当跳闸时,它不会执行预定义的操作。
CyclicBarrier(int parties, Runnable barrierAction):创建一个新的cycle barrier,当给定数量的参与方(线程)等待它时,它将跳闸即消除屏障,当跳闸时,它会进入执行的barrierAction线程先执行预定操作;
常用方法:
从复用性上来说,CountDownLatch是一次性的,CyclicBarrie是可复用的;
从阻塞线程数量上来说:CyclicBarrie必须阻塞所有子线程;CountDownLatch更加自由,可只阻塞一个主线程,也可阻塞所有子线程;
从根本思路上说:CountDownLatch只是一个计数器,当计数器计数为0时,受他堵塞的线程得到释放;而CyclicBarrie是一组线程间的相互等待,它的堵塞想要得到释放必须要所有参于线程都到达指定屏障;
https://github.com/cc6688211/concurrent-study.git