并发笔记(六)多线程步调一致,CountDownLatch、CyclicBarrier、Phaser、Exchanger

一.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:两个线程阻塞式的进行资源交互功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值