java线程池原理详解

1 概述

在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源,创建线程亦是如此,这导致在高并发中效率低下并且资源耗费严重,因此,池化资源技术应运而生,所以就有了线程池。

2 Executors类

JDK为我们封装了一套操作多线程的框架Executors,常用的方法如下:

newSingleThreadExecutor:
创建一个单线程的线程池,这个线程池有且只有一个线程在工作
newFixedThreadPool:
创建固定大小的线程池,通过带参数创建指定线程数大小,每提交一个任务就会创建一个新的线程,直到达到指定线程数
newCachedThreadPool:
创建一个可缓存的线程池,如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60 秒不执行任务)的线程
newScheduledThreadPool:
创建一个无限大小的线程池,每提交一个请求创建一个线程,支持定时和周期性任务执行

其实这些方法最终都是创建了ThreadPoolExecutor 对象,只是传入的参数不同

3 线程池的实现原理

在《阿里巴巴java开发规范》中是禁止使用Executors类创建线程池,必须通过合理设定ThreadPoolExecutor对象参数来创建线程池
看ThreadPoolExecutor源码有这几个参数

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

corePoolSize

核心线程数,当有任务进来的时候,如果当前线程数还未达到 corePoolSize 个数,则创建核心线程,核心线程有几个特点:

  1. 当线程数未达到核心线程最大值的时候,新任务进来,即使有空闲线程,也不会复用,仍然新建核心线程
  2. 核心线程一般不会被销毁,即使是空闲的状态,但是如果通过方法allowCoreThreadTimeOut(boolean value) 设置为 true 时,超时也同样会被销毁
  3. 生产环境首次初始化的时候,可以调用prestartCoreThread() 方法来预先创建所有核心线程,避免第一次调用缓慢

maximumPoolSize

除了有核心线程外,有些策略是当核心线程完全无空闲的时候,还会创建一些临时的线程来处理任务,maximumPoolSize 就是核心线程 + 临时线程的最大上限。临时线程有一个超时机制,超过了设置的空闲时间没有事儿干,就会被销毁。

keepAliveTime

这个就是上面两个参数里所提到的超时时间,也就是线程的最大空闲时间,默认用于非核心线程,通过 allowCoreThreadTimeOut(boolean value) 方法设置后,也会用于核心线程。

unit

这个参数是用来指定keepAliveTime参数的时间单位,秒、分、时等。

workQueue

它是 BlockingQueue类,即阻塞队列。
当核心线程满了并且没有空闲的了,新来的任务就会被放到这个等待队列中。这个参数其实一定程度上决定了线程池的运行策略,为什么这么说呢,因为队列分为有界队列和无界队列。
有界队列: 队列的长度有上限,当核心线程满载的时候,新任务进来进入队列,当达到上限,如果没有核心线程去即时取走处理,这个时候,就会创建临时线程。(警惕临时线程无限增加的风险)
无界队列: 队列没有上限的,当没有核心线程空闲的时候,新来的任务可以无止境的向队列中添加,而永远也不会创建临时线程。(警惕任务队列无限堆积的风险)

threadFactory

它是一个接口,用于实现生成线程的方式、定义线程名格式、是否后台执行等等,可以用 Executors.defaultThreadFactory() 默认的实现即可,也可以使用自定义的线程工厂

handler

当没有空闲的线程处理任务(即核心线程满了,并且都是在运行状态),并且等待队列已满(这里只对有界队列有效),再有新任务进来的话,就要做一些取舍了,而这个参数就是指定取舍策略的,ThreadPoolExecutor类提供了下面四种策略可以选择:
AbortPolicy:直接抛出异常,这是默认策略
DiscardPolicy:直接丢弃任务,但是不抛出异常
DiscardOldestPolicy:丢弃队列最前面的任务,然后将新来的任务加入等待队列
CallerRunsPolicy:由线程池所在的线程处理该任务,比如在 main 函数中创建线程池,如果执行此策略,将有 main 线程来执行该任务

整个线程池的执行过程如下图所示
java线程池原理详解_第1张图片

(1)用户向线程池提交一个任务,线程池首先判断是否有空闲线程,如果有则使用空闲线程处理任务,
(2)如果没有空闲线程,则判断核心线程数corePoolSize是否满了,没满则创建新的线程处理任务
(3)如果核心线程满了,则判断队列workQueue是否满了,如果没满将任务添加到队列中等待有空闲线程来执行任务
(4)如果队列满了,则判断总容量maximumPoolSize是否满了,如果没满则创建临时线程处理任务,临时线程默认情况下,处于空闲状态时间超过keepAliveTime数则被回收
(5)如果总容量maximumPoolSize满了则执行线程拒绝策略handler

3.1 线程池提交任务

向线程池提交任务的方法有两种,分别是submitexecute
源码如下:
java线程池原理详解_第2张图片
java线程池原理详解_第3张图片
从源码可以看出execute方法是提交一个普通的Runnable对象,submit方法既可以提交Runnable对象也可以提交Callable对象,并且具有返回值返回一个Future对象,这个Future对象可以用来判断线程是否执行成功。所以当我们需要判断线程是否执行成功时,可以采用submit方法提交任务,再调用返回的Future对象的get()方法我们最终需要的返回值;如果不关心任务是否执行成功,使用execute方法即可

3.2线程池都有哪些状态

• RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。
• SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。
• STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
• TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。
• TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。

你可能感兴趣的:(java基础)