一、使用线程池的好处
1.降低资源消耗
可以重复利用已创建的线程降低线程创建和销毁造成的消耗。
2.提高响应速度
当任务到达时,任务可以不需要等到线程创建就能立即执行。
3.提高线程的可管理型
线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低稳定性,使用线程池,统一分配,调优和监控。
二、线程池的工作原理
当任务提交以后,线程池是如何处理的?
1.线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则执行第二步。
2.线程池判断工作队列是否已经满了,如果工作队列没有满,则将新提交的任务存储在工作队列里进行等待。如果工作队列满了,则执行第三步。
3.线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个工作线程来执行任务,如果已经满了,则交给饱和策略来处理这个任务。
三、解读线程池源码
java.uitl.concurrent.ThreadPoolExecutor 类是线程池中最核心的一个类,因此如果要透彻地了解 Java 中的线程池,必须先了解这个类。
ThreadPoolExecutor 的构造函数
构造器中各个参数的意义:
• corePoolSize:核心池的大小;
• maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在
线程池中最多能创建多少个线程;
• keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有
当线程池中的线程数大于 corePoolSize 时,keepAliveTime 才会起作用,直到线程池中
的线程数不大于 corePoolSize,即当线程池中的线程数大于 corePoolSize 时,如果一个
线程空闲的时间达到 keepAliveTime,则会终止,直到线程池中的线程数不超过
corePoolSize。但是如果调用了 allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于 corePoolSize 时,keepAliveTime 参数也会起作用,直到线程池中的线程数为 0
• unit:参数 keepAliveTime 的时间单位,有 7 种取值,在 TimeUnit 类中有 7 种静态属
性:(天、小时、分钟、秒、毫秒、微秒、纳秒)
• workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对
线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue;基于数组的先进先出队列,此队列创建时必须指定大小;
LinkedBlockingQueue;基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为 Integer.MAX_VALUE;
SynchronousQueue;这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
ArrayBlockingQueue 和 PriorityBlockingQueue 使用较少,
一般使用LinkedBlockingQueue 和 Synchronous。线程池的排队策略与 BlockingQueue 有关。
• threadFactory:线程工厂,主要用来创建线程;
• handler:表示当拒绝处理任务时的策略,(如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理)有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出 RejectedExecutionException 异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行
任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
由源码知道ThreadPoolExecutor 继承了AbstractExecutorService;
AbstractExecutorService 是一个抽象类,它实现了 ExecutorService 接口;
ExecutorService 又是继承了 Executor 接口;
Executor 是一个顶层接口,在它里面只声明了一个方法 execute(Runnable),返回值为void,参数为 Runnable 类型,从字面意思可以理解,就是用来执行传进去的任务的;然后 ExecutorService 接口继承了 Executor 接口,并声明了一些方法:submit、invokeAll、 invokeAny 以及 shutDown 等;
在 ThreadPoolExecutor 类中有几个非常重要的方法:
execute()方法实际上是 Executor 中声明的方法,在 ThreadPoolExecutor 进行了具体的
实现,这个方法是 ThreadPoolExecutor 的核心方法,通过这个方法可以向线程池提交一
个任务,交由线程池去执行。
submit()方法是在 ExecutorService 中声明的方法,在 AbstractExecutorService 就已经
有了具体的实现,在 ThreadPoolExecutor 中并没有对其进行重写,这个方法也是用来向
线程池提交任务的,但是它和 execute()方法不同,它能够返回任务执行的结果,去看
submit()方法的实现,会发现它实际上还是调用的 execute()方法,只不过它利用了
Future 来获取任务执行结果.
线程池的关闭方法:
• shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终
止,但再也不会接受新的任务
• shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
四、线程池的种类以及应用场景
4.1newCachedThreadPool
底层:返回一个ThreadPoolExecutor实例,corePoolSize为0,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L,unit为timeUnit.SECONDS;workQueue为synchronousQueue(同步队列)
通俗:当有新任务到来时,则插入到同步队列中,就会在池中找可用线程来执行,若有可用线程来执行,若没有可用线程,则创建一个线程来执行任务,若池中线程空闲时间超过指定的时间,则线程就会被销毁。
使用:执行很多短期异步的小程序或者负载较轻的服务器。
4.2newFixedThreadPool
底层:接受的参数为指定线程数量的nThread,corePoolSize为nThread,maximumPoolSize为nThread,keepAliveTime为0L,unit为TimeUnit.MILLISECONDS;workQueue为new LinkedBlockingQueue()无界阻塞队列;
通俗:创建可容纳固定数量线程的池子,线程的存活时间是无限的,当池子满了就不再添加线程了。如果池中所有线程都在执行任务,对于新任务会进入阻塞队列。
适用:执行长期的任务,性能好很多。
4.3newSingleThreadExecutor
底层:corePoolSize为1,maximumPoolSize为1;keepAliveTime为0L,workQueue为:new LinkedBlockingQueue()
通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正执行任务时,对于新任务进入阻塞队列当中。
适用:一个任务一个任务之执行的场景。
4.4newScheduledThreadPool
底层:不同的是workQueue为new DelayedWorkQueue()一个按超时时间升序排列的队列。
适用:周期性执行任务的场景。