生产环境如何使用java线程池

ThreadPoolExecutor谁都会用,但是如何用的好、用的对,可能就仁者见仁、智者见智~

吹之前,还是把ThreadPoolExecutor简单介绍一下:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler)

参数介绍:

  • corePoolSize :线程池的核心池大小,在创建线程池之后,线程池默认没有任何线程。当有任务过来的时候才会去创建创建线程执行任务。换个说法,线程池创建之后,线程池中的线程数为0,当任务过来就会创建一个线程去执行,直到线程数达到corePoolSize 之后,就会被到达的任务放在队列中(说明:除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程)。
  • maximumPoolSize :线程池允许的最大线程数,表示最大能创建多少个线程。maximumPoolSize>=corePoolSize。
  • keepAliveTime :表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
  • workQueue :一个阻塞队列,用来存储等待执行的任务,当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能
  • threadFactory :线程工厂,用来创建线程。
  • handler :表示当拒绝处理任务时的策略。

现在回归主题,这些参数怎么设置才算合理?越多越好?肯定不是,太多甚至会导致灾难;越少越好?也不是;

其实这是要根据具体的业务场景来判断的,分别从任务的角度、空间时间的角度去吹:

如何设置合理的线程数

1、任务一般分为:CPU密集型、IO密集型、混合型,对于不同类型的任务需要分配不同大小的线程池

  • CPU密集型:尽量使用较小的线程池,一般Cpu核心数+1;因为CPU密集型任务CPU的使用率很高,若开过多的线程,只能增加线程上下文的切换次数,带来额外的开销
  • IO密集型:方法一:可以使用较大的线程池,一般CPU核心数 * 2;IO密集型CPU使用率不高,可以让CPU等待IO的时候处理别的任务,充分利用cpu时间;方法二:线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。举个例子:比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:
    最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目

如何设置合理的队列大小

2、时间空间的限制

  • 基于空间 :比如队列可以占用10M内存,每个请求大小10K ,那么workQueue队列长度为1000合适
  • 基于时间 :对于单个线程,如果请求超时时间为1s,单个请求平均处理时间10ms,那么队列长度为100合适

不成文强制规定

1、线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象弊端如下:

  • FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
  • CacheThreadPool和ScheduledThreadPool:允许创建线程数量为Integer.MAX_VALUE,可能会创建大量线程,从而导致OOM。

2、给线程起个名字

线上不出bug,随便起啥名字你都无所谓,但如果产生bug了,大量的日志中,合理的线程名对于问题的定位还是很有帮助的。

3、重视线程中抛出的异常

为啥要重视,看个例子:

package com.thread.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class LostThreadException  implements Runnable{
    @Override
    public void run() {
        throw new RuntimeException();
    }
    //现象:控制台打印出异常信息,并运行一段时间后才停止
    public static void main(String[] args){
        //就算把线程的执行语句放到try-catch块中也无济于事
        try{
            System.out.println("Begin executor.....!");
            ExecutorService exec = Executors.newCachedThreadPool();
            exec.execute(new LostThreadException());
            System.out.println("Finish executor.....!");
        }catch(RuntimeException e){
            System.out.println("Exception happened!");
        }
    }
}

运行结果:

Begin executor.....!
Exception in thread "pool-1-thread-1" java.lang.RuntimeException
	at com.thread.test.LostThreadException.run(LostThreadException.java:14)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
Finish executor.....!

我们发现System.out.println("Exception happened!");并没有打印出来,即:异常在main线程中没有catch到;这就是你为什么要自己主动去处理线程中抛出的异常;开个玩笑,如果你在生产中产生了上述的现象,唯一的线索是:"pool-1-thread-1“,这是线程名,自动生成的名称,是不是很崩溃,如果你有良好的习惯,按照2中所说,自定义线程名,最起码你知道这个异常是哪个类型的线程池中的线程抛出来的,否则只能望洋兴叹,愁眉苦脸~

关于这种异常处理,网上有很多讲解,实现方式感觉不是太优雅,大家可以自己去体会~

发布了142 篇原创文章 · 获赞 345 · 访问量 45万+

猜你喜欢

转载自blog.csdn.net/zhengchao1991/article/details/84927774