线程池的堆栈问题

看下面的例子:

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[] args){
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(0,Integer.MAX_VALUE,0L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
        for (int i = 0;i < 5;i++){
            poolExecutor.submit(new DivTask(100,i));
        }
    }
}

上述代码是将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 R u n n a b l e A d a p t e r . c a l l ( E x e c u t o r s . j a v a : 511 ) a t j a v a . u t i l . c o n c u r r e n t . F u t u r e T a s k . r u n ( F u t u r e T a s k . j a v a : 266 ) a t j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r . r u n W o r k e r ( T h r e a d P o o l E x e c u t o r . j a v a : 1142 ) a t j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r 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 RunnableAdapter.call(Executors.java:511)atjava.util.concurrent.FutureTask.run(FutureTask.java:266)atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)atjava.util.concurrent.ThreadPoolExecutorWorker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
复制代码
可以看出得到的堆栈信息与上面使用execute()方法几乎一致,都只能知道异常在哪里抛出的。

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

复制代码

public class TraceThreadPoolExecutor extends ThreadPoolExecutor {

    public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    public void execute(Runnable task) {
        super.execute(wrap(task,clientTrace(),Thread.currentThread().getName()));
    }

    @Override
    public Future<?> submit(Runnable task) {
        return super.submit(wrap(task,clientTrace(),Thread.currentThread().getName()));
    }

    private Exception clientTrace(){
        return new Exception("Client stack trace!");
    }

    private Runnable wrap(final Runnable task,final Exception clientStack,String clientThreadName){
        return new Runnable() {
            @Override
            public void run() {
                try {
                    task.run();
                }catch (Exception e){
                    clientStack.printStackTrace();
                    throw e;
                }
            }
        };
    }
}

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

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

复制代码

//测试
    public static void main(String[] args){
        ThreadPoolExecutor poolExecutor = new TraceThreadPoolExecutor(0,Integer.MAX_VALUE,0L,TimeUnit.SECONDS,new SynchronousQueue<Runnable>());

        for (int i =0;i < 5;i++){
            poolExecutor.execute(new DivTask(100,i));
        }
    }

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

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. r u n ( T r a c e T h r e a d P o o l E x e c u t o r . j a v a : 30 ) a t j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r . r u n W o r k e r ( T h r e a d P o o l E x e c u t o r . j a v a : 1142 ) a t j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r 1.run(TraceThreadPoolExecutor.java:30) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor 1.run(TraceThreadPoolExecutor.java:30)atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)atjava.util.concurrent.ThreadPoolExecutorWorker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
100.0
50.0
33.0
25.0

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

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以通过以下方式配置 ConfigurableServletWebServerFactory 来设置 Tomcat 的 NIO 线程池堆栈大小: 1. 在应用程序的配置文件(例如 application.properties 或 application.yml)中添加以下属性: ``` server.tomcat.max-threads=<max-threads> server.tomcat.max-connections=<max-connections> server.tomcat.accept-count=<accept-count> server.tomcat.min-spare-threads=<min-spare-threads> server.tomcat.max-spare-threads=<max-spare-threads> server.tomcat.max-http-header-size=<max-http-header-size> ``` 其中: - `<max-threads>` 是 Tomcat 的 NIO 线程池中的最大线程数。 - `<max-connections>` 是 Tomcat 的最大连接数。 - `<accept-count>` 是 Tomcat 接受的最大连接数。 - `<min-spare-threads>` 是 Tomcat NIO 线程池中的最小空闲线程数。 - `<max-spare-threads>` 是 Tomcat NIO 线程池中的最大空闲线程数。 - `<max-http-header-size>` 是 Tomcat 支持的最大 HTTP 标头大小。 2. 创建一个 ConfigurableServletWebServerFactory bean,并设置上述属性: ``` @Bean public ConfigurableServletWebServerFactory webServerFactory() { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); factory.addConnectorCustomizers(connector -> { connector.setMaxThreads(<max-threads>); connector.setMaxConnections(<max-connections>); connector.setAcceptCount(<accept-count>); connector.setProperty("maxHttpHeaderSize", <max-http-header-size>); }); factory.addThreadPoolCustomizers(tp -> { tp.setMinSpareThreads(<min-spare-threads>); tp.setMaxSpareThreads(<max-spare-threads>); }); return factory; } ``` 3. 将创建的 ConfigurableServletWebServerFactory bean 注入到 Spring 应用程序中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值