什么是线程池?
线程池是多线程处理的一种形式,在处理过程中将任务提交给线程池,任务的执行由线程池管理。
如果为每个请求创建和处理线程,服务器资源将立即耗尽。 线程池可以减少线程的创建和销毁次数,并允许每个工作线程重用以执行多项任务。
为什么要使用线程池?
创建和销毁线程的费用相对较大,这些时间可能比处理业务的时间更长。 除了如此频繁地创建和销毁线程外,业务工作线程还会占用系统资源的时间,从而导致系统资源不足。 (可以消除创建和销毁线程的过程。)
线程池有什么作用?
线程池的作用是限制系统中运行的线程数。
1、提高效率,创建一定数量的线程放入池中,需要使用时从池中提取一个。 这比在需要时创建线程对象快得多。
2、可以创建易于管理的线程池托管代码,以相同的方式管理池中的线程。 例如,启动时该程序创建100个线程,每次有请求时分配线程进行工作。 如果正好同时有101个请求,则额外的请求可以排队等待,并且可以通过无休止地创建线程来防止系统崩溃。
说说几种常见的线程池及使用场景
1、newSingleThreadExecutor
创建单线程线程池,以便只在唯一的工作线程上执行任务,并且所有任务都按指定的顺序(FIFO、LIFO、优先级)执行。
2、newFixedThreadPool
创建用于控制线程并发最大数量的固定长度线程池,超出的线程将在队列中等待。
3、newCachedThreadPool
创建可缓存的线程池,如果线程池的长度超过处理所需的长度,则灵活回收可用线程;如果无法回收,则创建新线程。
4、newScheduledThreadPool
创建固定长度的线程池以支持计划和定期任务的执行。
线程池中的几种重要的参数
corePoolSize是线程池中的核心线程数,这些核心线程仅在未使用时回收
maximumPoolSize是线程池中可以存储的最大线程数
keepAliveTime是线程池中除核心线程之外的其他最长的可保留时间。 在线程池中,核心线程在没有任务的情况下也无法清除,因此剩下的线程有生存时间,这意味着核心线程以外可以保留的最长空闲时间
util是计算这段时间的单位。
工作队列是指等待队列,任务可以等待存储在任务队列中并执行。 执行的是FIFIO原则“先进先出”。
threadFactory是用于创建线程的线程工厂。
handler是一种拒绝策略,可以在任务已满后拒绝执行特定任务。
说说线程池的拒绝策略
如果请求任务层出不穷,系统此时无法处理,我们应该采取的策略是拒绝服务。 RejectedExecutionHandler接口提供了拒绝任务处理的自定义方法的机会。 ThreadPoolExecutor已经包含四种处理策略。
AbortPolicy策略:此策略直接抛出异常,防止系统正常运行。
CallerRunsPolicy策略:策略直接在被调用线程上执行当前放弃的任务,除非线程池已关闭。
DiscardOleddestPolicy策略:此策略尝试放弃最旧的请求(即将执行的任务),然后重新发送当前任务。
分散策略:此策略安静地放弃无法处理的任务,不处理任何内容。
除了JDK默认提供的四个拒绝策略之外,我们还可以根据自己的业务需求定制拒绝策略。 自定义方法很简单,直接实现RejectedExecutionHandler接口就可以了。
execute和submit的区别?
在前面的说明中,我们执行任务的是execute方法,除了execute方法外,还有submit方法可以执行提交的任务。
这两种方法的区别是什么? 在什么场合分别适用? 做个简单的分析吧。
execute适合不需要关注返回值的场景,将线程丢到线程池中执行即可。
submit方法适用于需要关注返回值的场景
五种线程池的使用场景
newSingleThreadExecutor :单线程线程池。 可以在需要确保顺序执行的场景中使用,并且只能运行一个线程。
newFixedThreadPool :一个固
定大小的线程池,可以用于已知并发压力的情况下,对线程数做限制。
newCachedThreadPool:一个可以无限扩大的线程池,比较适合处理执行时间比较小的任务。
newScheduledThreadPool:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。
newWorkStealingPool:一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。
线程池的关闭
关闭线程池可以调用shutdownNow和shutdown两个方法来实现
shutdownNow:对正在执行的任务全部发出interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表。
shutdown:当我们调用shutdown后,线程池将不再接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务。
初始化线程池时线程数的选择
如果任务是IO密集型,一般线程数需要设置2倍CPU数以上,以此来尽量利用CPU资源。
如果任务是CPU密集型,一般线程数量只需要设置CPU数加1即可,更多的线程数也只能增加上下文切换,不能增加CPU利用率。
上述只是一个基本思想,如果真的需要精确的控制,还是需要上线以后观察线程池中线程数量跟队列的情况来定。
线程池都有哪几种工作队列
1、ArrayBlockingQueue
是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
2、LinkedBlockingQueue
一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列
3、SynchronousQueue
一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
4、PriorityBlockingQueue
一个具有优先级的无限阻塞队列。