一.CountDownLatch
案例:对账系统的对账功能,用户下单之后生成订单记录,物流会生成派送订单,防止少发或者是多发,定时调度任务,查询未对账的订单和派送订单进行对账,对于存在差异的生成插入差异库中。
初始思路:
while(存在未对账订单){
// 查询未对账订单
pos = getPOrders();
// 查询派送单
dos = getDOrders();
// 执行对账操作
diff = check(pos, dos);
// 差异写入差异库
save(diff);
}
显然上诉是一个串行的功能,提升性能,首先想到利用多线程,如何拆分呢?
第一想法拆出两个线程单独去做查询功能?图片地址
while(存在未对账订单){
// 查询未对账订单
Thread T1 = new Thread(()->{
pos = getPOrders();
});
T1.start();
// 查询派送单
Thread T2 = new Thread(()->{
dos = getDOrders();
});
T2.start();
// 等待T1、T2结束
T1.join();
T2.join();
// 执行对账操作
diff = check(pos, dos);
// 差异写入差异库
save(diff);
}
上诉代码存在问题,每次都需要创建线程和销毁线程,浪费性能,使用线程池,并且使用CountDownLatch协调两个线程的步调一致性。
优化后:
// 创建2个线程的线程池
Executor executor = Executors.newFixedThreadPool(2);
while(存在未对账订单){
// 计数器初始化为2
CountDownLatch latch = new CountDownLatch(2);
// 查询未对账订单
executor.execute(()-> {
pos = getPOrders();
latch.countDown();
});
// 查询派送单
executor.execute(()-> {
dos = getDOrders();
latch.countDown();
});
latch.await();
// 执行对账操作
diff = check(pos, dos);
// 差异写入差异库
save(diff);
}
二.CyclicBarrier
可以理解为循环栅栏,相对于CountDownLatch来说,明显优势就是可以循环使用,并且在满足栅栏的条件的时候支持同步执行回调函数。
在对账的案例中,我们可以进一步优化,将对账和保存的功能也单独的起一个线程,这样在对账的时候,也不会影响查询待对账订单和查询派送订单。
查询待对账、派送订单为生产者,对账以及保存作为消费者—生产者消费者模型
// 订单队列
Vector<P> pos;
// 派送单队列
Vector<D> dos;
// 执行回调的线程池
Executor executor =
Executors.newFixedThreadPool(1);
final CyclicBarrier barrier =
new CyclicBarrier(2, ()->{
executor.execute(()->check());
});
void check(){
P p = pos.remove(0);
D d = dos.remove(0);
// 执行对账操作
diff = check(p, d);
// 差异写入差异库
save(diff);
}
void checkAll(){
// 循环查询订单库
Thread T1 = new Thread(()->{
while(存在未对账订单){
// 查询订单库
pos.add(getPOrders());
// 等待
barrier.await();
}
});
T1.start();
// 循环查询运单库
Thread T2 = new Thread(()->{
while(存在未对账订单){
// 查询运单库
dos.add(getDOrders());
// 等待
barrier.await();
}
});
T2.start();
}
三.Phaser
Phaser翻译为阶段,也就是,我们可定义不同阶段达到条件之后做什么事情,更像是CyclicBarrier的加强版本。
而且Phaser比CyclicBarrier更加灵活,因为给出初始量之后,我们在每个阶段都可以减少或者是新增线程数。
简单介绍相关方法:想要使用好还是有难度的,以后有时间可以扩充
public class Test2 {
static Random r = new Random();
static MyPhaser phaser = new MyPhaser();
static void milliSleep(int milli){
try {
TimeUnit.MILLISECONDS.sleep(milli);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
phaser.bulkRegister(8);
for (int i = 0; i < 5; i++) {
new Thread(new Person("person"+i)).start();
}
new Thread(new Person("司仪")).start();
new Thread(new Person("新郎")).start();
new Thread(new Person("新娘")).start();
}
static class Person implements Runnable{
String name;
public Person(String name){
this.name = name;
}
public void arrive(){
Test2.milliSleep(r.nextInt(1000));
System.out.printf("%s到达现场 ",name);
phaser.arriveAndAwaitAdvance();
}
public void eat(){
Test2.milliSleep(r.nextInt(1000));
System.out.printf("%s吃完 ", name);
phaser.arriveAndAwaitAdvance();
}
public void leave(){
Test2.milliSleep(r.nextInt(1000));
System.out.printf("%s离开 ",name);
phaser.arriveAndAwaitAdvance();
}
public void hug(){
if(name.equals("新郎") || name.equals("新娘")){
Test2.milliSleep(r.nextInt(1000));
System.out.printf("%s洞房 ",name);
phaser.arriveAndAwaitAdvance();
}else{
phaser.arriveAndDeregister();//到达注销
}
}
@Override
public void run() {
arrive();
eat();
leave();
hug();
}
}
static class MyPhaser extends Phaser{
/**
*
* @param phase 当前阶段
* @param registeredParties 完成当前阶段的人数
* @return 是否结束流程
*/
@Override
protected boolean onAdvance(int phase, int registeredParties) {
switch (phase){
case 0:
System.out.println("所有人都到齐了"+registeredParties);
return false;
case 1:
System.out.println("所有人都吃完了"+registeredParties);
return false;
case 2:
System.out.println("所有人都离开了"+registeredParties);
return false;
case 3:
System.out.println("婚礼介绍,新郎新娘入洞房"+registeredParties);
return true;
default:
return true;
}
}
}
}
输出结果:
Phaser未在项目中应用,内部提供的功能很多的方法,感兴趣的可以深入了解上诉只是简单介绍了,如何创建以及两个方法arriveAndAwaitAdvance以及arriveAndDeregister方法。
四.Exchanger
两个线程相互交互数据的。
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
new Thread(()->{
String s = "T1";
try {
s=exchanger.exchange(s);//线程阻塞的状态
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" : "+s);
},"t1").start();
new Thread(()->{
String s = "T2";
try {
s=exchanger.exchange(s);//线程阻塞的状态
// 也提供了,超时的方法
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" : "+s);
},"t2").start();
}
总结
CountDownLatch:多线程之间同步的,不可复用。
CyclicBarrier:相对于CountDownLatch,可以复用,并且达到"栅栏"的限制条件,可以执行回调方法(同步的);
Phaser:相对于CyclicBarrier,也同样可以复用,但是是分阶段的,不同阶段的条件值是可以变化的,并且在达到不同阶段的时候可以执行不同的回调方法,功能更加多样化。
Exchanger:两个线程阻塞式的进行资源交互功能。