通过前几篇文章的学习,你们对多线程应该有些了解了吧,这里附上前三篇文章的连接,尚未看过的小伙伴快去复习吧~~java
多线程基础篇入门git
线程的生命周期和经常使用 APIsgithub
生产者消费者问题面试
那相信你们也能感觉到,其实用多线程是很麻烦的,包括线程的建立、销毁和调度等等,并且咱们平时工做时好像也并无这样来 new 一个线程,实际上是由于不少框架的底层都用到了线程池。数据库
线程池是帮助咱们管理线程的工具,它维护了多个线程,能够下降资源的消耗,提升系统的性能。多线程
而且经过使用线程池,咱们开发人员能够更好的把精力放在任务代码上,而不去管线程是如何执行的,实现任务提交和执行的解藕。框架
本文将从是何、为什么、如何的角度来说解线程池:less
线程池是一种池化的技术,相似的还有数据库链接池、HTTP 链接池等等。async
池化的思想主要是为了减小每次获取和结束资源的消耗,提升对资源的利用率。工具
好比在一些偏远地区打水不方便的,你们会每段时间把水打过来存在池子里,这样平时用的时候就直接来取就行了。
线程池同理,正是由于每次建立、销毁线程须要占用太多系统资源,因此咱们建这么一个池子来统一管理线程。用的时候从池子里拿,不用了就放回来,也不用你销毁,是否是方便了不少?
Java 中的线程池是由 juc
即 java.util.concurrent
包来实现的,最主要的就是 ThreadPoolExecutor
这个类。具体怎么用咱们下文再说。
在多线程的第一篇文章中咱们说过,进程会申请资源,拿来给线程用,因此线程是很占用系统资源的,那么咱们用线程池来统一管理线程就可以很好的解决这种资源管理问题。
好比由于不须要建立、销毁线程,每次须要用的时候我就去拿,用完了以后再放回去,因此节省了不少资源开销,能够提升系统的运行速度。
而统一的管理和调度,能够合理分配内部资源,根据系统的当前状况调整线程的数量。
那总结来讲有如下 3 个好处:
说了这么多,终于到了今天的重点,咱们来看下究竟怎么用线程池吧~
Java 给咱们提供了 Executor
接口来使用线程池。
咱们经常使用的线程池有这两大类:
它俩的区别呢,就是第一个是普通的,第二个是能够定时执行的。
固然还有其余线程池,好比 JDK 1.7 才出现的 ForkJoinPool
,能够把大任务分割成小任务来执行,最后再大一统。
那么任务提交到一个线程池以后,它会经历一个怎样的过程呢?
线程池在内部实际上采用了生产者消费者模型(还不清楚这个模型的在文章开头有改文章的连接)将线程和任务解藕,从而使线程池同时管理任务和线程。
当任务提交到线程池里以后,须要通过如下流程:
BlockingQueue
,在生产者消费者这节里提到过。咱们主要说下 ThreadPoolExecutor
,它是最经常使用的线程池。
这里咱们能够看到,这个类里有 4 个构造方法,点进去仔细看,其实前三个都 call 了最后一个,因此咱们只须要看最后一个就好。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { ... }
这里咱们来仔细看下这几个参数:
corePoolSize the number of threads to keep in the pool, even if they are idle, unless { \@code allowCoreThreadTimeOut} is set
maximumPoolSize the maximum number of threads to allow in the pool
keepAliveTime when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.
unit the time unit for the { \@code keepAliveTime} argument
workQueue the queue to use for holding tasks before they are executed.
threadFactory the factory to use when the executor creates a new thread
handler the handler to use when execution is blocked because the thread bounds and queue capacities are reached
因此咱们能够经过本身传入这 7 个参数构造线程池,固然了,贴心的 Java 也给咱们包装好了几类线程池能够很方便的拿来使用。
咱们具体来看每一个的含义和用法。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
这里咱们能够看到,
Integer.MAX_VALUE
;它的适用场景在源码里有说:
These pools will typically improve the performance of programs that execute many short-lived asynchronous tasks.
来看怎么用:
public class newCacheThreadPool { public static void main(String[] args) { // 建立一个线程池 ExecutorService executorService = Executors.newCachedThreadPool(); // 向线程池提交任务 for (int i = 0; i < 50; i++) { executorService.execute(new Task());//线程池执行任务 } executorService.shutdown(); } }
执行结果:
能够很清楚的看到,线程 一、二、三、五、6 都很快重用了。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
这个线程池的特色是:
它的适用场景是:
Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.
public class FixedThreadPool { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 200; i++) { executorService.execute(new Task()); } executorService.shutdown(); } }
这里我限制了线程池里最多有 10 个线程,哪怕有 200 个任务须要执行,也只有 1-10 这 10 个线程能够运行。
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
这个线程池顾名思义,里面只有 1 个线程。
适用场景是:
Creates an Executor that uses a single worker thread operating off an unbounded queue.
咱们来看下效果。
public class SingleThreadPool { public static void main(String[] args) { ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 100; i++) { executorService.execute(new Task()); } executorService.shutdown(); } }
这里在出结果的时候我可以明显的感受到有些卡顿,这在前两个例子里是没有的,毕竟这里只有一个线程在运行嘛。
因此在使用线程池时,其实都是调用的 ThreadPoolExecutor
这个类,只不过传递的不一样参数。
这里要特别注意两个参数:
workQueue
的选择,这个就是阻塞队列的选择,若是要说还得这么一大篇文章,以后有机会再写吧。handler
的设置。那咱们发现,在上面的 3 个具体线程池里,其实都没有设定 handler
,这是由于它们都使用了 defaultHandler
。
/** * The default rejected execution handler */ private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
在 ThreadPoolExecutor
里有 4 种拒绝策略,这 4 种策略都是 implements
了 RejectedExecutionHandler
:
AbortPolicy
表示拒绝任务并抛出一个异常 RejectedExecutionException
。这个我称之为“正式拒绝”,好比你面完了最后一轮面试,最终接到 HR 的拒信。DiscardPolicy
拒绝任务但不吭声。这个就是“默拒”,好比大部分公司拒简历的时候都是默拒。DiscardOldestPolicy
顾名思义,就是把老的任务丢掉,执行新任务。CallerRunsPolicy
直接调用线程处理该任务,就是 VIP 嘛。因此这 3 种线程池都使用的默认策略也就是第一种,光明正大的拒绝。
好了以上就是本文的全部内容了。固然线程池还有不少知识点,好比 execute()
和 submit()
方法,线程池的生命周期等等。
但随着阅读量的逐渐走低,齐姐意识到了这彷佛有什么误会,因此这篇文章是多线程系列的最后一篇了。
本文已收录至个人 Github 上:https://github.com/xiaoqi6666/NYCSDE
,点击阅读原文直达,这个 Github 汇总了我全部的文章和资料,以后也会一直更新和维护,还但愿你们帮忙点个 Star
,大家的支持和承认,就是我创做的最大动力!
我是小齐,终生学习者,天天晚上 9 点,云自习室里不见不散!