`
flychao88
  • 浏览: 743576 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

一段JAVA线程池的设置引发的血案

 
阅读更多

       应公司需求我们对一个项目进行了线上压力测试,结果发现,三台服务器一共只有59TPS,结果惨不忍睹。

那么针对这样的场景,我们利用一周时间进行专注性的优化,寻找性能的瓶颈点。

 

第一步:我们针对线上的环境进行模拟,尽量真实的在测试环境中再现,采用数据库连接池为咱们默认的C3P0。

那么当压测到二万批,100个用户同时访问的时候,并发量突然降为零!报错如下:

Could not get JDBC Connection; nested exception is java.sql.SQLException: An attempt by a client to checkout a Connection has timed out.

 

针对以上错误跟踪C3P0源码,以及在网上搜索资料(http://blog.sina.com.cn/s/blog_53923f940100g6as.html)发现,C3P0在大并发下表现的性能不佳。

 

第二步:针对这个问题进行数据库连接池优化,更换了BoneCPDataSource,以及Apache BasicDataSource后,发现报错如下:

           

java.lang.OutOfMemoryError: unable to create new native thread, dubbo version: 2.5.4, current host: 192.168.122.1
java.lang.OutOfMemoryError: unable to create new native thread

 

第三步:由此可以判断,问题不在于连接池的问题,于是在压测的时候,将DUMP日志导出进行分析发现,项目中启动了一万多个线程,而且每个线程都极为忙碌,彻底将资源耗尽。

             于是迅速定位到代码,发现如下代码:

         

private static final ExecutorService executorService = Executors.newCachedThreadPool();
/**
* 异步执行短频快的任务
* @param task
*/
public static void asynShortTask(Runnable task){
executorService.submit(task);
//task.run();
}

           CommonUtils.asynShortTask(new Runnable() {
                @Override
                public void run() {
                    String sms = sr.getSmsContent();
                    sms = sms.replaceAll(finalCode, AES.encryptToBase64(finalCode, ConstantUtils.getDB_AES_KEY()));
                    sr.setSmsContent(sms);
                    smsManageService.addSmsRecord(sr);
                }
            });

 

 

那么问题到底在哪里呢???就在这一行!

private static final ExecutorService executorService = Executors.newCachedThreadPool();

 

在并发的情况下,无限制的申请线程资源造成性能严重下降,在图表中显抛物线形状的元凶就是它!!!那么采用这种方式最大可以产生多少个线程呢??答案是:Integer的最大值!看如下源码:

public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>(),
                                  threadFactory);
}

 

那么尝试修改成如下代码:

private static final ExecutorService executorService = Executors.newFixedThreadPool(50);

 

 

修改完成以后,并发量重新上升到100以上TPS,但是当并发量非常大的时候,项目GC(垃圾回收能力下降),分析原因还是因为 Executors.newFixedThreadPool(50)这一行,虽然解决了产生无限线程的问题,但是

当并发量非常大的时候,采用newFixedThreadPool这种方式,会造成大量对象堆积到队列中无法及时消费,看源码如下:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

 可以看到采用的是无界队列,也就是说队列是可以无限的存放可执行的线程,造成大量对象无法释放和回收。

  

结论:

目前我们的项目还在持续优化中,还没有最终优化完成,目标是要把项目优化完善,但此次事件,再次提醒我们,在使用线程池的时候,一定要把握其细节,深入了解其原理再使用,不要随意使用,任何线程池的使用方式都有不同的使用场景,并不是只要使用了线程池就万事大吉,还有很多工作需要我们去注意。

分享到:
评论
2 楼 steve_JoX 2016-01-19  
“将DUMP日志导出进行分析发现,项目中启动了一万多个线程,而且每个线程都极为忙碌,彻底将资源耗尽”,你这里的这句话,我想问一下,你怎么统计出当前的线程数的。
1 楼 viscent 2015-10-25  
从经验角度来说,这种现象的问题可以优先使用jstack或者jvisualvm看下线程的情况。这样可以快速找到有问题的线程,并通过其callstack定位到有问题的代码(具体的方法)。线程池最大size一般要考虑机器的CPU个数,不知道你们测试机器是什么配置?
另外,线程池可以考虑“反压”的效果,即线程池使用有界队列缓冲任务,这样可以减少GC负担。

相关推荐

Global site tag (gtag.js) - Google Analytics