单线程实现数据导入功能!!太丢人了!

        由于开发时间紧张,在做一个数据导入的功能时只采用的简单的单线程导入,在功能上线后,用户导入上万条数据就要10分钟以上,作为一个“经验丰富”的程序员,写出这样的程序确实丢大人了。为了提升用户体验,顺便挽回一点颜面,决定采用多线程方式实现来优化一下这个功能。

 

        既然要采用多线程,那就离不开线程池的使用,为什么,因为线程池可以减少创建线程和销毁线程所消耗的时间和系统开销,能采用当然尽量采用。那接下来采用什么线程池呢?这让我想起了很多面试官喜欢问的:创建线程池有几种方式?请分别介绍一下。对这个不了解的可以查一下资料。不过这里没有采用这其中任何一种线程池,而是自定义方式创建的线程池,更加灵活。实现过程比较简单,将遍历执行导入数据的任务放入线程池执行即可。方式也分两种,一种是将任务按固定数量分组,每组作为一个任务启动一个线程。另一种是每遍历一个任务,就启动一个线程。

 

        在导入5千条左右数据的时候,线程池参数配置相同的情况下,测试了两种情况的执行时间,基本上一致,这与我最初的设想不太一致,最初我认为分组将任务放入线程池一定会比逐一放入效率更高,因为毕竟能够同时执行任务的线程最大数基本是固定的,那么逐一放入的任务在等待执行时势必有更多任务要加入队列,待可以执行时从队列中取出,入队和出队的过程也是需要时间消耗的,而放入队列的任务越多,时间消耗也越大。之所以这两种方式时间差别不大,或许是因为数据量还不够大。但从内存占用的角度,还是建议尽量采用将任务分组执行。

        接下来就是关于核心线程数的设置,简单来说就是:

        CPU密集型,配置公式:CPU核数+1

        IO密集型,配置公式:CPU核数*2

 

        那么如何区分任务是CPU密集型还是IO密集型呢?

        CPU密集型就是该任务需要大量运算,没有阻塞,一直全速运行(大量的计算)。

        IO密集型就是任务需要大量的IO,即大量阻塞(数据的读取写入、网络IO数据传输)。

 

        这个功能除了导入数据外,还要提示成功了多少条,失败了多少条,及导入失败数据的行数。大家都知道多个线程操作一个共享变量势必会造成并发问题,导致数据不准确。好在java程序提供了高并发下保持原子性操作的类型和集合,如AtomicInteger、AtomicLong、Collections.synchronizedList,可以收集每个线程的执行结果。当然也可以用Callable/Future的方式获取每个线程的返回结果,再叠加到一起。

 

        上述多个线程一定是要全部执行完才接着执行主线程,这里用到了CountDownLatch类,这个类使用起来很简单,在多线程开启之前定义好CountDownLatch c = new CountDownLatch(size),size为任务数。在每个线程的finally代码块中c.countDown();最后在多线程代码下面的主线程里使用c.wait() 等待全部子线程运行完毕,再执行主线程。

 

        经过优化后的数据导入程序,导入效率提高了80%,效果明显。

 

        无论是编写程序还是做其他事情,都要有一个以终为始的思考方式,就拿实现这个导入数据功能来说,最终的成品是什么,一定是希望导入数据准确无误,校验严密,效率又很高。那么针对这些点就可以采取相应的实现方式。而不是写一步想一步,写到最后发现,功能的确是实现了,但是效率太差,用户体验太差,属于半成品。

    

        每个追求成长的人,每天都要做一些对于自己来说有些难度的事情,即使实在找不到有难度的事,可以把能做的事争取做到完美,这个程中也会有所提高。

posted @ 2020-08-27 12:14  元飞贤彦  阅读(169)  评论(0)    收藏  举报