线程池(Thread Pool):是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。
由于创建(需要分配内存等资源)和销毁(垃圾回收器在后台一直跟踪并销毁)线程是非常耗费时间的,在并发情况下对性能的影响很大。运用线程池技术则很好地解决了这一问题。
一个线程池管理了一组工作线程,同时它还包括了一个用于放置等待执行任务的任务队列(阻塞队列)。
概念理解:
1、核心线程(corePool):线程池最终执行任务的角色肯定是线程,同时我们也会限制线程的数量,所以我们可以这样理解核心线程,有新任务提交时,首先检查核心线程数,如果核心线程都在工作,而且数量已经达到最大核心线程数,则不会继续新建核心线程,而会将任务放入等待队列。
2、等待队列(workQueue):等待队列用于存储当核心线程都处于运行状态时,继续新增的任务。核心线程在执行完当前任务后,会去等待队列拉取任务继续执行,这个队列一般是一个线程安全的阻塞队列,它的容量是由开发者根据业务来定制。
3、非核心线程:当等待队列满了,如果当前线程数没有超过最大线程数,则会新建线程执行任务,其实本质上核心线程和非核心线程没有区别。创建出来的线程也没有标识去区分它们是核心的还是非核心的,线程池只会去判断已有的线程数(包括核心和非核心)去跟核心线程数和最大线程数比较,来决定下一步的策略。
4、线程活动保持时间(keepAliveTime):线程空闲下来之后,保持存活的持续时间,超过这个时间还没有任务执行,该工作线程结束。
5、饱和策略(RejectedExecutionHandler):当等待队列已满,线程数也已达最大线程数,线程池会根据饱和策略来执行后续的操作,默认策略是抛弃要加入的任务。
图解线程池运作流程:
当有新任务提交到线程池时,线程池的处理流程如下:
①判断核心线程池是否已满。如果没满,则创建新线程来执行此任务(即使当前有空闲的线程也会直接创建,而不是使用空闲线程来执行),直到核心线程池中的线程数达到了设置的大小之后就不再创建;如果核心线程池已满,则进入下一阶段的判断。
②判断等待队列是否已满。如果没满,则将任务暂时存放到等待队列中,等待核心线程池中的线程空闲下来再来获取任务执行(核心线程池中的线程执行完任务之后会循环从等待队列中取任务来执行);如果队列已满,则进入下一阶段的判断。
③判断线程池是否已满。(线程池除了核心线程池,还设置了线程池的最大线程大小,即使核心线程池满了,还可以再创建线程),如果线程池中工作的线程没有达到最大值,则创建新线程来执行任务;如果线程池已满,则按照饱和策略来处理任务。
使用线程池的好处:
①提高响应速度(减少了创建新线程的时间)
②降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
③提高线程的可管理性:对线程进行统一分配和监控,避免无限制创建线程导致内存溢出或者耗尽CPU
应用场景:
①需要大量线程,并且完成任务时间短
②对性能要求苛刻
③接收突发性的大量请求
使用注意事项:
①线程池的大小:并非越多越好。应根据系统运行的硬件环境以及应用本身的特点来决定线程池的大小。一般来说,如果代码结构合理,线程数与cpu数量相适合即可。如果线程运行时可能出现阻塞现象,可响应增加线程池的大小,如果有必要可以采用自适应算法来动态调整线程池的大小,以提高cpu的有效利用率和系统的整体性能。
②并发错误:多线程应用要特别注意并发错误,要从逻辑上保证程序的正确性,注意避免死锁现象的发生。
③线程泄露:这是线程池应用中的一个严重的问题,当任务执行完毕而线程没能返回线程池中就会发生线程泄露现象。