java线程池底层运行过程以及参数详解

发布时间:2022-03-01 11:58:04 作者:yexindonglai@163.com 阅读(426)

相信线程池这个东东只要只要是做过开发的童鞋们都熟悉,面试也是经常会问到的一个问题,用起来那是相当地简单啊,而且功能强劲,优化的又好,使用还去除了冗余的代码层级,提高了代码的可读性,创建线程池只需要用到四种

  1. newCachedThreadPool      创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  2. newFixedThreadPool          创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  3. newScheduledThreadPool  创建一个定长线程池,支持定时及周期性任务执行。
  4. newSingleThreadExecutor  创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

随便拿出一个来实例化举例下,创建线程的代码是这样的

  1. // 创建指定大小的线程池,池里最多只能有2个线程
  2. ExecutorService executorService = Executors.newFixedThreadPool(2);
  3. // 创建新线程
  4. executorService.execute(new Runnable() {
  5. @Override
  6. public void run() {
  7. System.out.println("我是线程池创建出来的线程");
  8. }
  9. });

进入源码看看,我们可以看到,其实线程池用的是 ThreadPoolExecutor 这个类

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

在看看 ThreadPoolExecutor 这个类的构造函数,发现它其实是有7个参数的

  1. public ThreadPoolExecutor(int corePoolSize,
  2. int maximumPoolSize,
  3. long keepAliveTime,
  4. TimeUnit unit,
  5. BlockingQueue<Runnable> workQueue,
  6. ThreadFactory threadFactory,
  7. RejectedExecutionHandler handler) {
  8. }

 

线程池底层执行流程图

参数说明

那这 7 个参数分别代表什么呢?这就到了我们文章的重点部分了,别着急,我们一个个地详细解答;

1、corePoolSize  【核心线程数】

核心线程数就是线程池实际的线程数量,且这些线程一旦开启后就不会轻易关闭,队列中的任务执行时优先使用空闲的核心线程,直到超过 keepAliveTime 时间后才会关闭;

2、 maximumPoolSize  【最大线程数】

线程池能 容纳的最大线程数量,当核心线程满了,并且队列也满了之后,在来新任务的话,就会使用最大线程数的空间来创建新线程;

3、 keepAliveTime  【空闲线程存活时间】

核心线程在执行完任务后不会立马关闭,而是直到超过 keepAliveTime 时间后才会关闭,如果把 keepAliveTime 为 0 ,那么执行完任务后会立马回收(这一点很多博主都写错了,说是设置为0将永不回收,其实是错误的)

4、unit    【空闲线程存活时间单位】

keepAliveTime 的时间单位,主要有以下几种:

  •         TimeUnit.DAYS                      天
  •         TimeUnit.HOURS                  小时
  •         TimeUnit.MINUTES              分钟
  •         TimeUnit.SECONDS             秒
  •         TimeUnit.MILLISECONDS    毫秒
  •         TimeUnit.MICROSECONDS 微秒
  •         TimeUnit.NANOSECONDS  纳秒

5、 workQueue  【等待执行的任务队列】

当核心线程运行的任务数量已经满了之后,那么在添加进来的任务就进入 workQueue 队列中,待核心线程任务执行完后会依次执行队列中的任务;等待执行的队列遵循先进先出,后进后出的原则,就是先加进来的任务优先执行;默认任务队列使用的 ConcurrentLinkedQueue 队列,是一个无界的非阻塞队列;无界是没有界限的意思,大小不受限制,会自动扩容;就像动态数组一样;

6、 threadFactory   【创建新线程时使用的工厂】

创建新线程时使用的工厂类,默认使用  DefaultThreadFactory ,可自定义工厂,只需要继承  ThreadFactory 接口即可;可以用来设定线程名、是否为daemon(守护线程)等等 ,

7、 handler   【拒绝策略】

当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,拒绝策略需要实现   RejectedExecutionHandler 接口,jdk中提供了4种拒绝策略 

  1.   CallerRunsPolicy  直接在主线程运行被拒绝的任务,除非线程池已关闭,已通过实验证实;我们看看源代码,r.run() 里面的 r 其实就是主线程

  2. AbortPolicy  直接丢弃任务,并抛出RejectedExecutionException异常;

  3. DiscardPolicy  直接丢弃任务,什么也不做,看源码就知道,里面啥也没有

  4. DiscardOldestPolicy  抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列

当然除了jdk自带的策略外,你也可以通过实现 RejectedExecutionHandler 接口来自定义策略;

 

 

配置线程池

要合理地配置线程池,根据代码密集型进行配置即可

  • CPU密集型:
    • 任务需要大量运算,没有阻塞的情况,cpu全速运行;
    • 配置最大线程数 = CPU核数
  • IO密集型 : 
    • 大量访问io。产生阻塞,尽量使用多线程技术;
    • 配置最大线程数 = 2 * CPU核数

 

关键字Java