多线程处理业务
最近在网上看到许多的业务场景,明明可以高效率的完成业务,但还是有很多人习惯了老方式处理业务,导致处理时间过慢,响应时间过慢,用户体验变差。
这篇文章,来介绍一下使用多线程来处理业务。
分析问题
首先来描述一个业务场景:某系统需要校对数据,校对订单和库存信息,校对之后存库,然后存库再到页面显示。
总体流程为:先查询订单记录,然后在查询库存的扣减记录,然后对比订单和库存扣减记录,然后将校对的结果信息保存到数据校对信息表中,看下图。
让我们看下平常代码处理的一个逻辑,并且大家可以想一下有什么诟病?
可能很多小伙伴已经发现了,这里我们一起来分析一下这个业务处理。
假设每个业务处理时长为上图所示,那么整个业务处理下来的时间是:
由上图可以看出,getOrder()和getInventory() 耗时是比较长的,并且他们都是独立的,先后顺序也不影响结果。那我们可以使用并行来执行这个两个方法吗?很显然是可以的,我们讲这两个方法放到不同的两个线程中,可以优化执行效率。
优化后,我们将getOrder()放到线程1,getInventory() 放到线程2,线程3执行 checkData()和saveData()。
然后我们用代码去实现一下,继续往下看!
解决思路
我们可以分别开启两个线程执行getOrder()方法和getInventory()方法,在主线程中执行checkData()方法和saveData()方法。这里需要注意的是:主线程需要等待两个子线程执行完毕之后再执行checkData()方法和saveData()方法。
为了实现这个功能,我们可以使用Thread类中join()方法。
这里,具体的逻辑就是在主线程中调用两个子线程的join()方法实现阻塞等待,当两个子线程执行完毕退出时,调用两个子线程join()方法的主线程会被唤醒,从而执行主线程中的checkData()方法和saveData()方法。大体代码如下所示。changeData()只是将后一个结果赋值给前一个list1。
到这里,我们已经从耗时上优化了。看看还有没有优化空间?
进一步优化
使用多线程之后已经达到我们的目标,从耗时上已经优化一大截。但是我们应该还要考虑一个问题,就是空间消耗问题,上面if当中 每次执行getOrder()和getInventory() 都会新建两个线程,大家都知道新建线程在java当中是一个非常耗时的操作,随着操作次数频繁等等其他原因也会导致系统反应迟钝。所以我们需要创建能够反复使用的线程,没错!就是线程池。
新的问题
之前我们使用的使用的是Thread自带的方法 join() 方法进行阻塞,实现主线程等待子线程执行之后再使用子线程处理之后的数据进行后边业务逻辑。但是在线程池当中不可用join()。这也就是突破问题的关键所在。
新的解决方案
其实解决这个问题的方案有很多种,主要是知道一个核心逻辑:一个线程需要等待其他两个线程的逻辑执行完毕后再执行。
在java并发当中有很多的类都可以使用,接下来就介绍两种方法,使用线程池自带的延时等待,还有CountDownLatch类。
第一种方案 使用线程池延时解决
第二种 使用CountDownLatch类
在程序中首先创建一个CountDownLatch对象,计数器的值初始化为2。分别在子线程1和子线程2中调用latch.countDown()方法使得计数器的值分别减1。在主线程中调用latch.await()方法,等待计数器的值变为0,继续往下执行。
到这里,我才算完成最终的优化!
总结
主要是将单线程执行的数据校对业务,优化成使用多线程执行。在平时的工作过程中,我们需要认真思考,找到系统性能瓶颈所在,找出在逻辑上不相干,并且没有先后顺序的业务逻辑,将其放到不同的线程中执行,能够大大提供系统的性能。
最后说一下CountDownLatch主要的使用场景就是一个线程等待多个线程执行完毕后再执行。
到这里,也进一步提醒了我们:如果想学好并发编程,熟练的掌握Java中提供的并发类库是我们必须要做到的。
景就是一个线程等待多个线程执行完毕后再执行**。
到这里,也进一步提醒了我们:如果想学好并发编程,熟练的掌握Java中提供的并发类库是我们必须要做到的。