title: 【线程池01】-线程池的概念以及常见参数
date: 2018-12-06 12:16:12
tags:
- 多线程
- 线程池
- Executor
categories: Java高并发多线程
线程池作为「Java高并发多线程」里一个重要的知识点,在实际工作和面试过程中都有用到。
这里线程池第一篇将会整理一些线程池相关的概念,以及介绍线程池的各个参数。
什么是线程池(what)?
线程池是一种基于池化技术管理线程的方式,类似的还有很多:数据库连接池、Http连接池、对象池(内存池)。提前创建一批资源放到池子里,在使用的时候直接来进行资源的申请,节约了资源创建和释放的系统消耗,同时池子控制住大小,防止不断的资源申请导致系统奔溃。
参考美团技术推文:「线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等等,同时也降低了计算机的整体性能。线程池维护多个线程,等待监督管理者分配可并发执行的任务。这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。」
相比我的描述更加清晰准确些。
为什么要使用多线程(why)?
在传统的web开发中,服务器接收到请求后都需要一个线程来处理请求。
每次请求过来创建一个线程,使用起来也很方便,可是这样就会存在这样一个问题:
如果并发的请求数量非常多,但每个线程执行的时间很短,这样就会频繁的创建和销毁线程,如此一来会大大降低系统的效率。可能出现服务器在为每个请求创建新线程和销毁线程上花费的时间和消耗的系统资源要比处理实际的用户请求的时间和资源更多。
在这种情况下,线程池的存在就很有价值。线程池为线程生命周期的开销和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。
总结来说,线程池有如下优点:
降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
提高响应速度:任务到达时,无需等待线程创建即可立即执行。
提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
如何使用线程池(how)?
Java中主要是使用J.U.C提供的ThreadPoolExecutor类来使用线程池,这里主要先总结整理下线程池的几个常用方法以及线程池的常用参数,后面再针对具体内部的代码和使用做整理。
创建线程池都可以通过Executors类来实现,此类主要提供了一系列工厂方法来创建线程池,返回的线程池都实现了ExrcutorService接口。
//创建一个固定大小的线程池,超出的部分在队列等待
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
//创建一个单线程的线程池
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
//创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程(60s),若无可回收,则新建线程。
public static ExecutorService newCachedThreadPool()
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
//创建一个单线程线程池,支持定时及周期性任务执行
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
//创建一个定长线程池,支持定时及周期性任务执行
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
//会创建一个含有足够多线程的线程池,来维持相应的并行级别
//它会通过工作窃取的方式,使得多核的 CPU 不会闲置,总会有活着的线程让 CPU 去运行
public static ExecutorService newWorkStealingPool(int parallelism)
public static ExecutorService newWorkStealingPool()
但是,阿里开发手册有如下一条规定:
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式
这是因为 Executor.newCachedThreadPool() 和 Executors#newScheduledThreadPool() 两个方法最大线程数为Integer.MAX_VALUE,如果达到最大值,系统可能OOM。Executors.newSingleThreadExecutor() 和Executors.newFixedThreadPool() 两个方法的 workQueue 参数为new LinkedBlockingQueue<Runnable>(),容量为Integer.MAX_VALUE,如果队列非常大,也会有OOM风险。
所以一般情况下使用线程池使用ThreadPoolExecutor 提供的构造进行线程池创建,实际上Executors 提供的方法就是对此构造的封装。最底层的构造方法如下,下面将会依次介绍各个参数。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
核心参数说明
- corePoolSize
核心线程数,核心线程会一直存活,即使没有任务需要处理。当线程数小于核心线程数时,即使现有的线程空闲,线程池也会优先创建新线程来处理任务,而不是直接交给现有的线程处理。核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。 - maxPoolSize
最大线程数量,当线程数大于或等于核心线程,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。如果线程数已等于maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会拒绝处理任务而抛出异常。 | - keepAliveTime
线程最大存活时间,超过核心线程数的线程,如果在此参数指定的时间内还没有接收到新任务,则会被销毁。也就是说此参数是指定超过核心线程数的空闲线程的最大存活时间 | - workQueue
等待任务队列 ,用来保存通过execute(Runnable command)方法提交的未执行的任务。也就是在任务执行之前用来保存任务的队列。 | - threadFactory
线程工厂,生成线程的工厂类 - handler
线程池的饱和策略,如果阻塞队列满了并且没有空闲的线程,这时如果继续提交任务,就需要采取一种策略处理该任务
线程池提供了四种策略:
- AbortPolicy : 直接抛出RejectedExecutionException异常 这是默认策略;
- CallerRunsPolicy:用调用者所在的线程来执行任务;
- DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
- DiscardPolicy:直接丢弃任务;
具体的线程池使用示例会在下一篇中总结。