《Java并发编程实战》课程笔记(十二)

这篇具有很好参考价值的文章主要介绍了《Java并发编程实战》课程笔记(十二)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

CountDownLatch 和 CyclicBarrier:如何让多线程步调一致?

原始对账系统

  • 对账系统的业务简化后:
    • 首先用户通过在线商城下单,会生成电子订单,保存在订单库;
    • 之后物流会生成派送单给用户发货,派送单保存在派送单库。
    • 为了防止漏派送或者重复派送,对账系统每天还会校验是否存在异常订单。
  • 目前对账系统的处理逻辑是首先查询订单,然后查询派送单,之后对比订单和派送单,将差异写入差异库。
    《Java并发编程实战》课程笔记(十二)
  • 对账系统的核心代码如下,就是在一个单线程里面循环查询订单、派送单,然后执行对账,最后将写入差异库。
    while (存在未对账订单) {
    	// 查询未对账订单
    	pos = getPOrders();
    	// 查询派送单
    	dos = getDOrders();
    	// 执⾏对账操作
    	diff = check(pos, dos);
    	// 差异写⼊差异库
    	save(diff);
    }
    

利用并行优化对账系统

  • 对于串行化的系统,优化性能首先想到的是能否利用多线程并行处理。
    • 查询未对账订单 getPOrders() 和查询派送单 getDOrders() 是否可以并行处理呢?
    • 显然是可以的,因为这两个操作并没有先后顺序的依赖。
      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 实现线程等待

  • while 循环里面每次都会创建新的线程,而创建线程可是个耗时的操作。所以最好是创建出来的线程能够循环利用,线程池就能解决这个问题。
    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);
    }
    
  • 我们将 getPOrders() 和 getDOrders() 这两个查询操作并行了,但这两个查询操作和对账操作 check()、save() 之间还是串行的。很显然,这两个查询操作和对账操作也是可以并行的,也就是说,在执行对账操作的时候,可以同时去执行下一轮的查询操作。
    • 针对对账这个项目,我设计了两个队列,并且两个队列的元素之间还有对应关系。
    • 订单查询操作将订单查询结果插入订单队列,派送单查询操作将派送单插入派送单队列,这两个队列的元素之间是有对应关系的。
    • 两个队列的好处是,对账操作可以每次从订单队列出一个元素,从派送单队列出一个元素,然后对这两个元素执行对账操作,这样数据一定不会乱掉。
      《Java并发编程实战》课程笔记(十二)
    • ⼀个线程 T1 执行订单的查询工作,一个线程 T2 执行派送单的查询工作,当线程 T1 和 T2 都各自生产完 1 条数据的时候,通知线程 T3 执行对账操作。
    • 线程 T1 和线程 T2 只有都生产完 1 条数据的时候,才能一起向下执行,也就是说,线程 T1 和线程 T2 要互相等待,步调要一致。
    • 同时当线程 T1和 T2 都生产完一条数据的时候,还要能够通知线程 T3 执行对账操作。

用 CyclicBarrier 实现线程同步

  • 我们首先创建了一个计数器初始值为 2 的 CyclicBarrier,你需要注意的是创建 CyclicBarrier 的时候,我们还传入了一个回调函数,当计数器减到 0 的时候,会调用这个回调函数。
    • 线程 T1 负责查询订单,当查出一条时,调用 barrier.await() 来将计数器减 1,同时等待计数器变成 0;
    • 线程 T2 负责查询派送单,当查出一条时,也调用 barrier.await() 来将计数器减 1,同时等待计数器变成 0;
    • 当 T1 和 T2 都调用 barrier.await() 的时候,计数器会减到 0,此时 T1 和 T2 就可以执行下⼀条语句了,同时会调用 barrier 的回调函数来执行对账操作。
    • CyclicBarrier 的计数器有自动重置的功能,当减到 0 的时候,会自动重置你设置的初始值。
      // 订单队列
      Vector<P> pos;
      // 派送单队列
      Vector<D> pos;
      // 执⾏回调的线程池
      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();
      }
      

文章来源地址https://www.toymoban.com/news/detail-476429.html

到了这里,关于《Java并发编程实战》课程笔记(十二)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 5.5. Java并发工具类(如CountDownLatch、CyclicBarrier等)

    5.5.1 CountDownLatch CountDownLatch 是一个同步辅助类,它允许一个或多个线程等待,直到其他线程完成一组操作。 CountDownLatch 有一个计数器,当计数器减为0时,等待的线程将被唤醒。计数器只能减少,不能增加。 示例:使用CountDownLatch等待所有线程完成任务 假设我们有一个任务需

    2024年02月07日
    浏览(36)
  • 《C++并发编程实战》读书笔记(3):并发操作的同步

    当线程需要等待特定事件发生、或是某个条件成立时,可以使用条件变量 std::condition_variable ,它在标准库头文件 condition_variable 内声明。 wait() 会先在内部调用lambda函数判断条件是否成立,若条件成立则 wait() 返回,否则解锁互斥并让当前线程进入等待状态。当其它线程调用

    2024年02月10日
    浏览(26)
  • 《C++并发编程实战》读书笔记(4):原子变量

    标准原子类型的定义位于头文件 atomic 内。原子操作的关键用途是取代需要互斥的同步方式,但假设原子操作本身也在内部使用了互斥,就很可能无法达到期望的性能提升。有三种方法来判断一个原子类型是否属于无锁数据结构: 所有标准原子类型( std::atomic_flag 除外,因为

    2024年02月10日
    浏览(27)
  • 《C++并发编程实战》读书笔记(1):线程管控

    包含头文件 thread 后,通过构建 std::thread 对象启动线程,任何可调用类型都适用于 std::thread 。 启动线程后,需要明确是等待它结束、还是任由它独自运行: 调用成员函数 join() 会先等待线程结束,然后隶属于该线程的任何存储空间都会被清除, std::thread 对象不再关联到已结

    2024年02月10日
    浏览(30)
  • Java并发编程实战

    2023年06月19日
    浏览(39)
  • 《C++并发编程实战》读书笔记(2):线程间共享数据

    在C++中,我们通过构造 std::mutex 的实例来创建互斥量,调用成员函数 lock() 对其加锁,调用 unlock() 解锁。但通常更推荐的做法是使用标准库提供的类模板 std::lock_guard ,它针对互斥量实现了RAII手法:在构造时给互斥量加锁,析构时解锁。两个类都在头文件 mutex 里声明。 假设

    2024年02月10日
    浏览(33)
  • python:并发编程(十二)

    本文将和大家一起探讨python的多协程并发编程 (下篇) ,使用内置基本库asyncio来实现并发,先通过官方来简单使用这个模块。先打好基础,能够有个基本的用法与认知,后续文章,我们再进行详细使用。 本文为python并发编程的第十二篇,上一篇文章地址如下: python:并发

    2024年02月09日
    浏览(24)
  • java并发编程之美第五章读书笔记

    CopyOnWriteArrayList 线程安全的ArrayList,对其进行的修改操作都是在底层的一个复制的数组(快照)进行的,也就是写时复制策略 类图 每一个对象里面有一个array数组进行存放具体的元素,ReentrantLock独占锁对象用来保证同时只有一个线程对array进行修改,这里只要记得ReentrantLock是独占锁

    2024年02月03日
    浏览(35)
  • Java并发编程学习笔记(一)线程的入门与创建

    认识 程序由指令和数据组成,简单来说,进程可以视为程序的一个实例 大部分程序可以同时运行多个实例进程,例如记事本、画图、浏览器等 少部分程序只能同时运行一个实例进程,例如QQ音乐、网易云音乐等 一个进程可以分为多个线程,线程为最小调度单位,进程则是作

    2024年02月16日
    浏览(39)
  • 【项目实战】并发编程之Java中使用五种方式实现延时执行调用

    To实现延时执行调用,Java中可以使用Thread.sleep()方法。该方法接受一个以毫秒为单位的时间参数,使当前线程休眠指定的时间。在休眠期间,线程不会执行任何操作。 以下是一个示例代码块,演示如何使用Thread.sleep()方法实现延时执行调用:

    2024年02月15日
    浏览(26)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包