线程池和Executor框架
1. 线程池子的好处:
说起线程池,我们为啥要使用线程池呢。
首先我们来了解一下线程池的工作原理是什么:它一定是有好处我们才用它,线程池就是因为具有核心线程数,最大线程数,以及工作队列 和 拒绝策略,以及由工作线程变为空闲线程之后可存活的时间(KeepTimeAlive)。当一个任务提交给线程池以后,线程池首先首先判断核心线程池子是否已经满了,满了的话就装不下了,那就得找个容器把它装起来,这个容器就是任务队列。核心线程数没有满了的话就会产生相应的核心线程来执行这个任务。核心线程满了就存任务队列,如果任务队列也满了的话就创建普通的工作线程来执行它,如果最大线程数都满了的话肯定没有多余的线程来执行这个任务了,这个时候就需要执行拒绝策略了。这里待会来说拒绝策略。
这个时候线程池的好处
我们就可以看见了,当使用线程池的时候就使用空闲的线程而不用重新产生新的线程,也避免了线程因为空闲而销毁所造成的资源浪费
。所有还有就是我们使用了空闲的线程之后任务就可以快速的分配到空闲线程中去加快了响应速度
。线程池是一个池子,在这个池子中 我们可以统一的进行调优、监控和分配,所以方便了我们的管理
。
2. 线程池的参数
线程池子的参数有哪些呢这里我们可以看一下:
new ThreadPoolExecutor(corePoolSize,maximumPoolSize,
keepAliveTime,milliseconds,
runnableTaskQueue,
handler);
我们可以看到线程池它是由这六部分参数组成的,那么这几部分的参数分别代表什么含义,以及他们包含了哪些东西;
corePoolSize:核心线程池的大小;
maximumPoolSize:线程池最大线程数的大小;
keepAliveTime:变为空闲线程后可以存活的时间;
TimeUnit;线程存活时间的基本代为
runnableTaskQueue:任务队列;
handler:拒绝策略;
任务队列在不同的线程池上面采用不同的任务队列:任务队列有哪些呢?它是有4个任务队列组成的:ArrayBlockingQueue,LinkedBlokingQueue,SynchronousQueue,PriorityBlockingQueue.
其中ArrayBlockingQueue按先进先出的原则对元素进行的排序,它是基于数据结构的有界阻塞队列。
LinkedBlockingQueue是基于链表的阻塞队列,它的吞吐量要高于ArrayBlockingQueue,它一般用在newFixedTheadPool()中,一般这种线程池是使用在负载比较重的服务器中。
SynchronousQueue是不存储元素的无界队列,它的执行策略是必须有一个线程出去,才能有一个新的线程进入队列一般应用在newCacheTheadPool中。
PriorityBlockingQueue它的执行策略是对按线程优先级顺序进行排列的,执行线程时间最小的线程优先执行。
3.任务提交到线程池
线程提交到线程池中具有两种方式一种是execute()方法进行提交,另一种是submit()方式进行提交,这另种提交方式之间的区别就是execute()提交线程池的方式是没有返回值的提交,而submit()提交方式是有返回值的提交,返回一个future 对象,可以通过这个对象开查看任务在线程池中是否执行成功。
threadPool.execute( new Runnable(){
public void run(){
//todo
}
});
//submit方式的提交:
Future<Task> future = executor.submit(harReturnValueTask);
try{
object s = future.get();
}
catch(InterruptedException e){//中断处理异常}
catch(ExecutionException e){//处理无法执行任务异常}
finally{
executor.shutdown();
}
3.关闭线程池
当我们不需要线程池的时候需要对它进行关闭,那我们得先知道线程池的状态:它跟进程,以及线程的状态具有类似的地方都是具有5中状态: 分别是Running
,Shutdown
,stop
,tidying
,terminated
.
他们的过程如上图的样子,在这里有两个不同的地方就是执行shutDown和shutDownNow他们俩之间的区别,执行shutDown方法后,但是任务队列里面的任务得执行完才能转到tidying态,而执行shutDownNow方法就是任务到这就不用执行了直接进入tidying态,最后再执行Terminated方法来进入终止态。
4.合理的配置线程池
我们需要合理的配置线程池,因此要分析任务属性:
- 任务的性质:CPU密集型,IO密集型和混合任务
- 任务的优先级: 高,中,低。
- 任务的执行时间: 长,中,短
- 任务的依赖性:是否依赖其他系统资源,比如说数据库
我们一般对于CPU密集型的线程池分配的线程数量为Ncpu+1;因为CPU在高速执行,消耗的线程数的资源比较快,因此占用的线程数量比较小,对于IO密集型的我们可以使用2Ncpu个线程
按任务的优先级我们可以使用PriorityQueue来执行时间比较小的线程优先。我们一般的使用有界队列,因为使用无界队列当某些线程异常时,如SQL中都是出现了慢查询 会导致内存撑爆,从而导致整个系统崩溃。
5.线程的监控
我们可以通过一些参数来观测线程池里需要执行的任务数量,完成的任务数量,以及曾经创建过的最大线程数,线程池的线程数量,获取活动的线程数
- taskCount
- completeTaskCount
- largestPoolSize
- getPoolSize
- getActiveCount
6 Executor 框架
Java线程是工作单元,也是执行机制。在JDK1.5开始,把Runnable和Callable (工作单元)与执行机制Executor分离开来。
Executor主要有三大组件组成:任务,任务的执行,异步计算的结果过程如下图所示:
我们在这里说一下这三个阶段主要的成员:
Runnable,Callable,ThreadPoolExecutor,ShedulerThreadPoolExecutor,Future,以及Executors。
6.1 ThreadPoolExecutor:最核心的类
ThreadPoolExecutor主要是根据工厂类Executors来进行创建,Executors可以创建三种:
创建固定线程数量的线程池: newFixedThreadPool适用于负载重的服务器。
public static ExecutorService
newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue
<Runnable>());
}
因为采用了无界队列,因此所以不会有拒绝策略以及它的KeepAliveTime和最大
线程数将会失去作用;
创建单个线程的线程池 new SingleThreadPool
public static ExecutorService
newSingleThreadExecutor( ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
创建单个线程的跟固定线程数目的区别就是它的线程池的个数是1个。
创建缓存线程池:newCachedThreadPool 大小无界的线程池。适用于许多短期异步小程序或者负载比较轻的服务器。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue
<Runnable>());
}
执行过程:当空闲线程向同步队列取时,主线程执给空闲队列提供任务,当没有空闲线程的时候直接创建一个新的线程,当新的线程执行完任务会从同步队列中进行取,如果取不到则60s后挂掉。
6.2sheduledThreadPoolExecutor
一般使用工厂类Executors来创建, Executors一般可以创建两种SheduledThreadPoolExecutor如下:
包含若干线程的SheduledThreadPoolExecutor``以及SingleThreadSheduledExecutor
(只包含一个线程的SheduledThreadPoolExecutor
)
SheduledThreadPoolExecutor
适用于多个线程执行周期任务。
SingleThreadSheduledExecutor
适用于单个后台线程执行周期任务;
sheduledThreadPoolExecutor
采用的是delayQueue
作为阻塞队列,它的执行主要包含两个部分:
- 调用它的sheduleAtFixRate()方法的或者sheduleWithFixedDelay时,会向队列中添加一个RunnableScheduledFutur接口的sheduledFutureTask;
- 线程池中的线程从Delay中获取并执行
scheduledFutureTask包含的成员变量3个:
time,sequenceNumeber,period(执行的时间间隔周期)
delayQueue.take()的执行流程图:
Future Task详解:
它一般作用在等待别的任务执行完成之后再进行执行可以使用FutureTask。
FutureTask它实现了Runnable,以及Future接口,因
此可以通过利用通过Executor执行,也可以通过submit给ExecutorService返回一个FutureTask然后执行FutureTask.get,或者Cancel。它具有三种状态:未执行,正在执行,已经完成;
因此当执行它的get,以及Cancel方法的时候在三种状态下的状态如下图所示:
附件:
阻塞队列
ArrayBlockingQueue和LinkedBlockingQueue之前的区别,其中ArrayBlockingQueue是有界的队列,因为它的底层是居于数组的它创建的时候就会确定数组的大小,linkedBlockingQueue它可以是有界的也可以是无界的,当它是无界队列的时候,当put进去的任务,大于取出的任务时候就会造成内存的溢出。且ArrayBlockingQueue它的插入和删除不会产生任何对象实例,而LinkedBlockingQueue它的插入和删除就会造成实例的产生和消除。两者实现队列添加或者移除的锁不一样,ArrayBlockingQueue实现的队列中的锁是没有分离的,添加和删除采用的是同一个ReenterLock锁,而LinkedBlckingQueue它添加采用的是PutLock,移除采用的是takeLock,这样能大大的提高队列的吞吐量。在并发的情况下,生产者和消费者可以并行的操作队列中的数据,来提高整个队列的并发性。Array…使用的是全局锁,而Linked…使用的是局部锁。