线程池的堆栈问题

前面的文章已经讲了线程池线程池的内部实现,这篇文章来了解线程池出错的堆栈信息的打印,毕竟异常堆栈信息的重要性对于程序员来说就像是指南针对于茫茫大海上的船只一样,没有指南针船只只能更加艰难的寻找方向,没有异常堆栈信息,排查问题时,也就只能像大海捞针一样,慢慢琢磨了。

看下面的例子:

 1 public class DivTask implements Runnable {
 2
 3     int a,b;
 4     public DivTask(int a,int b){
 5         this.a = a;
 6         this.b = b;
 7     }
 8     @Override
 9     public void run() {
10         double re = a / b;
11         System.out.println(re);
12     }
13     //测试
14     public static void main(String[] args){
15         ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(0,Integer.MAX_VALUE,0L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
16         for (int i = 0;i < 5;i++){
17             poolExecutor.submit(new DivTask(100,i));
18         }
19     }
20 }

上述代码是将DivTask提交到线程池,从第16行for循环来看,我们会得到5个结果,分别是100除以i的商,下面就是这段代码的输出结果:

100.0
25.0
33.0
50.0

你没有看错,就只有4个结果,也就是说程序漏算了一组数据,但是更加不幸的是,没有任何的错误提示,就好像一切正常一样。但是在这个简单的案例中,只要你稍有经验,就能发现,作为除数i取到了0,这个缺失的值很可能是由于这个0导致的,但是如果是在稍微复杂的业务场景中,这种简单的错误足以让你几天萎靡不振。

也就是说:使用线程池虽然是件好事,但是得处处留意坑。线程池很可能会“吃”掉程序抛出的异常,导致我们对程序的错误一无所知。

改正方法:

1   最简单的方法,弃用submit(),改用execute()方法

将上述代码第17行修改为:

poolExecutor.execute(new DivTask(100,i));

这样执行代码后,你将得到部分堆栈信息,执行结果如下:

100.0
50.0
33.0
25.0
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
    at CurrentJava.DivTask.run(DivTask.java:10)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

注意了这里说的部分堆栈信息。这是因为从这两个异常堆栈中我们只知道异常在哪里抛出的(这里说的是第10行);但是我们还希望得到另外一个更重要的信息,那就是这个任务是在哪里提交的?而任务的具体提交位置已经被线程池给完全淹没了,顺着堆栈,我们最多只能找到线程调度的调度流程,而这对于我们来说几乎没有价值。

2   改造submit()方法

将上述代码第17行修改为:

Future re = poolExecutor.submit(new DivTask(100,i));
re.get();

执行后得到输出结果:

Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:192)
    at CurrentJava.DivTask.main(DivTask.java:17)
Caused by: java.lang.ArithmeticException: / by zero
    at CurrentJava.DivTask.run(DivTask.java:10)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

可以看出得到的堆栈信息与上面使用execute()方法几乎一致,都只能知道异常在哪里抛出的。

3   扩展 ThreadPoolExecutor

我们扩展ThreadPoolExecutor 线程池,让它在调度任务之前,先保存一下提交任务线程的堆栈信息,如下所示:

 1 public class TraceThreadPoolExecutor extends ThreadPoolExecutor {
 2
 3     public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
 4         super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
 5     }
 6
 7     @Override
 8     public void execute(Runnable task) {
 9         super.execute(wrap(task,clientTrace(),Thread.currentThread().getName()));
10     }
11
12     @Override
13     public Future<?> submit(Runnable task) {
14         return super.submit(wrap(task,clientTrace(),Thread.currentThread().getName()));
15     }
16
17     private Exception clientTrace(){
18         return new Exception("Client stack trace!");
19     }
20
21     private Runnable wrap(final Runnable task,final Exception clientStack,String clientThreadName){
22         return new Runnable() {
23             @Override
24             public void run() {
25                 try {
26                     task.run();
27                 }catch (Exception e){
28                     clientStack.printStackTrace();
29                     throw e;
30                 }
31             }
32         };
33     }
34 }

上述代码第21行,wrap()方法的第二个参数为一个异常,里面保存着提交任务的线程堆栈信息。该方法将我们传入的Runnable对象进行一层包装,使之能处理异常信息,当任务发生异常时,这个异常就会被打印(第28行)。

将第一个例子的main方法修改为:

1 //测试
2     public static void main(String[] args){
3         ThreadPoolExecutor poolExecutor = new TraceThreadPoolExecutor(0,Integer.MAX_VALUE,0L,TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
4
5         for (int i =0;i < 5;i++){
6             poolExecutor.execute(new DivTask(100,i));
7         }
8     }

执行,就可以得到下面的信息:

java.lang.Exception: Client stack trace!
    at CurrentJava.TraceThreadPoolExecutor.clientTrace(TraceThreadPoolExecutor.java:22)
    at CurrentJava.TraceThreadPoolExecutor.execute(TraceThreadPoolExecutor.java:13)
    at CurrentJava.DivTask.main(DivTask.java:22)
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
    at CurrentJava.DivTask.run(DivTask.java:14)
    at CurrentJava.TraceThreadPoolExecutor$1.run(TraceThreadPoolExecutor.java:30)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
100.0
50.0
33.0
25.0

可以看出,熟悉的异常又回来了!现在我们不仅可以得到异常发生的Runnable实现内的信息,我们也知道了这个任务是在哪里提交的。这样丰富的信息,我相信可以帮助我们瞬间定位问题。

参考: 《Java高并发程序设计》 葛一鸣 郭超 编著:

原文地址:https://www.cnblogs.com/Joe-Go/p/9754601.html

时间: 2024-08-21 03:32:27

线程池的堆栈问题的相关文章

在线程池中寻找堆栈

在线程池中寻找堆栈 下面看一个简单的例子: public class DivTask implements Runnable { int a, b; public DivTask(int a, int b) { this.a = a; this.b = b; } @Override public void run() { double re = a / b; System.out.println(re); } } 运行该任务: public static void main(String[] a

问题(一)---线程池,锁、堆栈和Hashmap相关

一.线程池: 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力. 假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间. 如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能. 一个线程池包括以下四个基本组成部分: 1.线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务: 2.工作线程(PoolWorker):线程

记5.28大促压测的性能优化&mdash;线程池相关问题

目录: 1.环境介绍 2.症状 3.诊断 4.结论 5.解决 6.对比java实现 废话就不多说了,本文分享下博主在5.28大促压测期间解决的一个性能问题,觉得这个还是比较有意思的,值得总结拿出来分享下. 博主所服务的部门是作为公共业务平台,公共业务平台支持上层所有业务系统(2C.UGC.直播等).平台中核心之一的就是订单域相关服务,下单服务.查单服务.支付回调服务,当然结算页暂时还是我们负责,结算页负责承上启下进行下单.结算.跳支付中心.每次业务方进行大促期间平台都要进行一次常规压测,做到心里

线程池,千万注意,原来很多人都在错用

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的堆栈大小,以默认的优先级运行. [C#线程池] 场景:以下是C# winform写的一个线程池示例程序.窗体上,分别拖放一个开始和停止按钮,单击开始按钮,for寻觅模拟7000个任务队列,在线程池运行过程中,可以点击停止按钮,来结束线程池所有任务,这里借助CancellationTokenSource对象,来实现线程池的停止. 开始前,请大家看以下代码,有什么问题

记5.28大促压测的性能优化—线程池相关问题

目录: 1.环境介绍 2.症状 3.诊断 4.结论 5.解决 6.对比java实现 废话就不多说了,本文分享下博主在5.28大促压测期间解决的一个性能问题,觉得这个还是比较有意思的,值得总结拿出来分享下. 博主所服务的部门是作为公共业务平台,公共业务平台支持上层所有业务系统(2C.UGC.直播等).平台中核心之一的就是订单域相关服务,下单服务.查单服务.支付回调服务,当然结算页暂时还是我们负责,结算页负责承上启下进行下单.结算.跳支付中心.每次业务方进行大促期间平台都要进行一次常规压测,做到心里

C#多线程--线程池(ThreadPool)

先引入一下线程池的概念: 百度百科:线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中.如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙.如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值.超过最大值的线程可以排队,但他们要等到其他线程

ThreadPool线程池

1.GetMaxThreads,GetMinThreads class Program { static void Main(string[] args) { int workerThreads; int completePortsThreads; ThreadPool.GetMaxThreads(out workerThreads, out completePortsThreads); Console.WriteLine("线程池中最大的线程数{0},线程池中异步IO线程的最大数目{1}&qu

Mysql线程池优化笔记

Mysql线程池优化我是总结了一个站长的3篇文章了,这里我整理到一起来本文章就分为三个优化段了,下面一起来看看. Mysql线程池系列一(Thread pool FAQ) 首先介绍什么是mysql thread pool,干什么用的?使用线程池主要可以达到以下两个目的:1.在大并发的时候,性能不会因为过载而迅速下降.2.减少性能抖动 thread pool的工作原理?线程池使用分而治之的方法来限制和平衡并发性.与默认的thread_handling不同,线程池将连接和线程划分开,所以连接数量和执

java线程池原理及实现方式

线程池的定义 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程 为什么要使用线程池 1.减少在创建和销毁线程上所花的时间以及系统资源的开销 2.在一个 JVM 里创建太多的线程可能会导致系统由于过度消耗内存而用完内存或"切换过度".为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻处理的请求数目. 线程池组成部分 1.线程池管理器(ThreadPoolManager):用于创建并管理线程池,包括 创建线程池,销