记一次基于生产者消费者模式的性能提升
接到一个项目要求,要求很简单---统计出文件加下文件中指定的某些指令的出现次数。
根据要求将问题分解为以下几块:
1、要统计的指令,这个用脚趾头想都要放到配置文件中,根据操作的文件语法格式,将不同模块类型下的指令以JSON数组的形式进行配置存放,格式如下
{"C_OPERS": ["MOVE","CLEAR","WRITE"], "D_DEFINE": ["DIM","S"]}
2、获取指定目录下所有文件,以文件数组返回, 采用递归方式获取。
private void scanAllDirs(String dirPath) {
File file = new File(dirPath);
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f: files) {
scanAllDirs(f.getAbsolutePath());
}
} else {
try {
queue.put(file.getAbsolutePath());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3、根据步骤2中得到的文件数组循环调用另外的解析模块代码进行解析,可以获取一个文件中指令的出现次数。
4、累计统计。
单个文调试无问题,由于文件数量比较多自然的想到了使用多线程来提升效率,采用典型的生产者消费者模式。
将生产者和消费者的比例设置为1:3,BlockingQueue初始值设置为1000,然后再套一层生产者消费者模式,总体就变成了
先生成文件,使用消费者消费文件后得到单个文件中指令出现的次数,用单个文件中出现的文件次数再进行一次生成,最后再调用新消费者,进行消费统计。
理想很美好,现实很残酷,运行程序发现还是很慢,2300多个文件运行了3个多小时居然还没统计完毕。
果断终止程序,分析到底是哪里导致的系统运行的这么慢。
第一、看文件解析所消耗的时间,单个文件解析的时间基本在之分之一秒内,然后将文件解析数量增加至10个,发现解析时间没有明显增加,看来解析文件不是导致程序慢的原因,暂时先放下。
第二、接下来就是生产者消费者模式了,这个是经历过n多java大师和前辈验证过的模式不会有什么问题
第三、BlockingQueue的初始值,初始值是根据感觉来设置的,1000是不是有点小,那就改2000试试,调试后涛声依旧。看来改大不行,暂时先放下
第四、把生成者模式改成1:1的最简单的方式把嵌套的二级生产者消费者模式去掉进行调试,程序运行还是很慢。
到此有点卡壳了。程序已经很简单了,那唯一可能的地方就是BlockingQueue的初始值了,因为以前感觉设置的越大越好。接下来反其道而行至,将BlockingQueue的初始值设置为100。
运行程序,黄天不付有心人,我对了,这次解析完所有文件只花费了26分钟,和之前的相比简直是天壤之别。然后再加入累计所有文件中指令的for循环。因为指令的个数是以key:value形式存储,
开始使用的是HashMap存储,跑一下时间在56分钟左右,这下换就比较清晰了,是HashMap导致的性能瓶颈,KO,换,使用并发包ConcurrentHashMap,再次运行时间重回30分钟内。
小结:问题的关键点在于初始化时装在的数据太多,使用生产者消费者模式和BlockingQueue解决了一次性装在数据太多的问题,然后再使用并发包提升性能。
隐含问题:当指令数量增加时程序还会变慢这个是当前的设计和要求所来了的必然问题,后续如果还要提升性能那就要重新设计。

浙公网安备 33010602011771号