文章目录
1、使用线程池的好处
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,不需要等到线程创建就能立即执行。
- 提高线程的可管理性。使用线程池可以进行统一的分配,调优和监控。
2、线程的创建与管理
- 通过继承Thread类来创建一个线程
- 实现Runnable接口并重写run方法,new Thread(runnable).start(),线程启动时就会自动调用该对象的run方法
- 实现Callable接口并重写call()方法,使用FutureTask类来包装Callable对象,使用FutureTask对象作为Thread对象的target创建并启动新线程;也可使用线程池启动
- 线程池:Executors 类提供了方便的工厂方法来创建不同类型的ExecutorService。无论是Runnable接口的实现类还是Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行
3、ThreadPoolExecutor参数详解
- corePoolSize:线程池核心大小
线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了keepAliveTime,如果运行线程池中的线程少于 corePoolSize,则创建新线程来处理任务,即使线程池中的其他线程是空闲的
- maximumPoolSize:线程池最大线程数量
一个任务被提交到线程池后,当前线程的数量大于corePoolSize,首先会缓存到工作队列中,如果工作队列满了,则会创建一个新线程,然后从工作队列中的取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize来指定。
- keepAliveTime:空闲线程存活时间
一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定
- unit:空闲线程存活时间单位
线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime就被销毁
- workQueue:阻塞队列
如果线程池中的线程数量大于等于corePoolSize的时候,把该任务封装成一个Worker对象放入阻塞队列
保存等待执行的任务的阻塞队列,当提交一个新的任务到线程池以后, 线程池会根据当前线程池中正在运行着的线程的数量来决定对该任务的处理方式,主要有以下几种处理方式:
直接切换:这种方式常用的队列是SynchronousQueue
使用无界队列:一般使用基于链表的阻塞队列LinkedBlockingQueue。如果使用这种方式,那么线程池中能够创建的最大线程数就是corePoolSize,而maximumPoolSize就不会起作用了。当线程池中所有的核心线程都是RUNNING状态时,这时一个新的任务提交就会放入等待队列中。无界队列最大长度为Integer.MAX_VALUE
使用有界队列:一般使用ArrayBlockingQueue。使用该方式可以将线程池的最大线程数量限制为maximumPoolSize,这样能够降低资源的消耗,但同时这种方式也使得线程池对线程的调度变得更困难,因为线程池和队列的容量都是有限的值,所以要想使线程池处理任务的吞吐率达到一个相对合理的范围,又想使线程调度相对简单,并且还要尽可能的降低线程池对资源的消耗,就需要合理的设置这两个数量。
workQueue,它用来存放等待执行的任务。BlockingQueue 是个接口,你需要使用它的实现之一是来使用BlockingQueue,java.util.concurrent包下具有以下BlockingQueue接口的实现类:
-
ArrayBlockingQueue:ArrayBlockingQueue是一个有界的阻塞队列,其内部实现是将对象放到一个数组里。有界也就意味着,它不能够存储无限多数量的元素。它有一个同一时间能够存储元素数量的上限。你可以在对其初始化的时候设定这个上限,但之后就无法对这个上限进行修改了,因为它是基于数组实现的,也就具有数组的特性:一旦初始化,大小就无法修改
-
LinkedBlockingQueue:LinkedBlockingQueue内部以一个链式结构(链接节点)对其元素进行存储。如果需要的话,这一链式结构可以选择一个上限。如果没有定义上限,将使用 Integer.MAX_VALUE 作为上限。
-
DelayQueue:DelayQueue对元素进行持有直到一个特定的延迟到期。注入其中的元素必须实现java.util.concurrent.Delayed接口
-
PriorityBlockingQueue:PriorityBlockingQueue是一个无界的并发队列。它使用了和类java.util.PriorityQueue一样的排序规则。你无法向这个队列中插入null值。所有插入到PriorityBlockingQueue的元素必须实现java.lang.Comparable接口。因此该队列中元素的排序就取决于你自己的Comparable实现
-
SynchronousQueue:SynchronousQueue是一个特殊的队列,它的内部同时只能够容纳单个元素。如果该队列已有一元素的话,试图向队列中插入一个新元素的线程将会阻塞,直到另一个线程将该元素从队列中抽走。同样,如果该队列为空,试图向队列中抽取一个元素的线程将会阻塞,直到另一个线程向队列中插入了一条新的元素
- threadFactory:线程工厂
它是ThreadFactory类型的变量,用来创建新线程。默认使用Executors.defaultThreadFactory() 来创建线程。使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY优先级并且是非守护线程,同时也设置了线程的名称,可以自定义线程的名字、优先级等。
- handler:拒绝策略
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,它是RejectedExecutionHandler类型的变量,线程池提供了4种策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常,这是默认策略
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
4、线程池的创建方式
Executors类提供了方便的工厂方法来创建不同类型的线程池。无论是Runnable接口的实现类还是Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行
4.1、newCachedThreadPool()
/**
* 创建一个可缓存线程池,如果线程池长度超过处理需求,可灵活回收空闲线程,若无可回收,则新建线程
*
* 阻塞队列使用的是SynchronousQueue,每来一个线程,不创建任务,直接往等待队列中放,每放一个,就去判断最大线程数,如果没有达到最大线程数,就去创建线程
* corePoolSize核心线程数为0,最大线程数为Integer.MAX_VALUE,允许的创建线程数量为Integer.MAX_VALUE,一旦线程池中线程的数量过多,那么服务器可能报OOM了
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
创建一个可缓存线程池,如果线程池长度超过处理需求,可灵活回收空闲线程,若无可回收,则新建线程,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能
4.2、newFixedThreadPool(int nThreads)
/**
* 创建一个定长的线程池,可以控制线程的最大并发数,超出的线程会在队列中等待
*
* corePoolSize核心线程数和最大线程数是一样的,通过创建时给出
* 阻塞队列使用的是LinkedBlockingQueue,它是是无界队列,可以不断往队列中添加Worker对象
* 如果线程数达到corePoolSize核心线程数大小,可以不断的往队列中添加,线程池的大小是固定的,任务可以不断的往阻塞队列中添加,一旦在阻塞队列中的过大,那么服务器内存将直接报OOM了
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
4.3、newSingleThreadExecutor()
/**
* 创建一个单线程化的线程池,用唯一的工作线程来执行任务,保证所有任务按照指定顺序去执行,执行顺序可以是先入先出,优先级等等
*
* 阻塞队列使用的是LinkedBlockingQueue,它是是无界队列,可以不断往队列中添加Worker对象
* corePoolSize和maximumPoolSize都是1,线程池中只有一个工作的线程,添加的任务都封装成Worker对象添加到阻塞队列中,任务可以不断的往阻塞队列中添加,一旦在阻塞队列中的过大,那么服务器内存将直接报OOM了
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
4.4、newScheduledThreadPool(int corePoolSize)
/**
* 创建一个定长的线程池,支持定时以及周期性的任务执行
*/
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
/**
* ScheduledThreadPoolExecutor继承了ThreadPoolExecutor
*
* 阻塞队列使用的是DelayedWorkQueue,对元素进行持有直到一个特定的延迟到期
* 允许的创建线程数量为Integer.MAX_VALUE,一旦线程池中线程的数量过多,那么服务器可能报OOM了
*/
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}
5、线程池的初始化
默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。在实际中如果需要线程池创建之后立即创建线程,ThreadPoolExecutor提供了两个方法支持
方法 | 方法描述 |
---|---|
prestartCoreThread() | 初始化一个核心线程 |
prestartAllCoreThreads() | 初始化所有核心线程 |
6、线程池的关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭
方法 | 方法描述 |
---|---|
shutdown() | 不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务 |
shutdownNow() | 立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务 |
7、线程池提交任务的执行过程
-
如果运行的线程少于corePoolSize,则创建新线程来处理任务,即使线程池中的其他线程是空闲的
-
如果线程池中的线程数量大于等于corePoolSize且小于maximumPoolSize,则只有当workQueue满时才创建新的线程去处理任务
-
如果设置的corePoolSize和maximumPoolSize相同,则创建的线程池的大小是固定的,这时如果有新任务提交,若workQueue未满,则将请求放入workQueue中,等待有空闲的线程去从workQueue中取任务并处理,如果workQueue队列中也满了的话,就会执行handler所指定的拒绝策略来处理任务
-
如果运行的线程数量大于等于maximumPoolSize,这时如果workQueue已经满了,则通过handler所指定的拒绝策略来处理任务;如果workQueue没满,继续添加到workQueue中
-
任务提交时,判断的参数顺序为corePoolSize --> workQueue --> maximumPoolSize
8、线程池总结
-
newCachedThreadPool()、newFixedThreadPool()、newSingleThreadExecutor()、newScheduledThreadPool()都是基于ThreadPoolExecutor()来实现线程池的,只不过是传的参数不一样
-
线程池提交任务的执行过程:
线程池刚创建时是没有线程存在的,当有线程需要创建时,直到达到核心线程数,会将所有线程放置workQueue队列中,如果workQueue是有界队列,等待队列满了的话,还有任务需要使用线程,将继续创建线程,直到达到最大线程数,如果超过了最大线程数,就会执行RejectedExecutionHandler指定的拒绝策略;如果workQueue是无界队列,那么任务可以不断的往等待队列中添加,一旦在等待队列中的过大,那么服务器内存将直接报OOM了
-
FixedThreadExecutor和SingleThreadExecutor线程池阻塞队列使用的都是LinkedBlockingQueue无界队列,如果阻塞队列中等待太多了任务,服务器将直接宕机或报OOM
-
CacheedThreadPool和ScheduledThreadPool线程池maximumPoolSize设置为Integer.MAX_VALUE,如果在运行的线程太多了,服务器将直接宕机或报OOM