线程池简介
所谓线程池,就是将多个线程放在一个池子里面(所谓池化技术),然后需要线程的时候不是创建一个线程,而是从线程池里面获取一个可用的线程,然后执行我们的任务。线程池的关键在于它为我们管理了多个线程,我们不需要关心如何创建线程,我们只需要关系我们的核心业务,然后需要线程来执行任务的时候从线程池中获取线程。任务执行完之后线程不会被销毁,而是会被重新放到池子里面,等待机会去执行任务。
在什么情况下使用线程池?
- 单个任务处理的时间比较短
- 将需处理的任务的数量大
假设一个服务器完成一项任务所需时间为:
T1创建线程时间,T2在线程中执行任务的时间,T3销毁线程时间。如果:T1 + T3远大于T2,则可以采用线程池,以提高服务器性能。一个线程池包括以下四个基本组成部分:
- 线程池管理器(
ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务 - 工作线程(
PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务 - 任务接口(
Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等 - 任务队列(
TaskQueue):用于存放没有处理的任务。提供一种缓冲机制。
- 线程池管理器(
使用线程池的好处:
- 减少在创建和销毁线程上所花的时间以及系统资源的开销
- 如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。
- 提交相应速度
工作流程
线程池的任务就在于负责这些线程的创建,销毁和任务处理参数传递、唤醒和等待。- 创建若干线程,置入线程池
- 任务达到时,从线程池取空闲线程
- 取得了空闲线程,立即进行任务处理
- 否则新建一个线程,并置入线程池,执行3
- 如果创建失败或者线程池已满,根据设计策略选择返回错误或将任务置入处理队列,等待处理
- 销毁线程池
风险
虽然线程池是构建多线程应用程序的强大机制,但使用它并不是没有风险的。
用线程池构建的应用程序容易遭受任何其它多线程应用程序容易遭受的所有并发风险,诸如同步错误和死锁,
它还容易遭受特定于线程池的少数其它风险,诸如与池有关的死锁、资源不足和线程泄漏。资源不足
线程池的一个优点在于:相对于其它替代调度机制而言,它们通常执行得很好。但只有恰当地调整了线程池大小时才是这样的。
线程消耗包括内存和其它系统资源在内的大量资源。除了Thread对象所需的内存之外,每个线程都需要两个可能很大的执行调用堆栈。
除此以外JVM可能会为每个Java线程创建一个本机线程,这些本机线程将消耗额外的系统资源。最后虽然线程之间切换的调度开销很小,
但如果有很多线程,环境切换也可能严重地影响程序的性能。
如果线程池太大,那么被那些线程消耗的资源可能严重地影响系统性能。在线程之间进行切换将会浪费时间,
而且使用超出比您实际需要的线程可能会引起资源匮乏问题,因为池线程正在消耗一些资源,而这些资源可能会被其它任务更有效地利用。
除了线程自身所使用的资源以外,服务请求时所做的工作可能需要其它资源,例如JDBC连接、套接字或文件。
这些也都是有限资源,有太多的并发请求也可能引起失效,例如不能分配JDBC连接。
线程池的使用
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类:
public class ThreadPoolExecutor extends AbstractExecutorService {
.....
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
...
}
上面构造器中各参数的含义:
corePoolSize:核心池的大小。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,
除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:TimeUnit.DAYSTimeUnit.HOURSTimeUnit.MINUTESTimeUnit.SECONDSTimeUnit.MILLISECONDSTimeUnit.MICROSECONDSTimeUnit.NANOSECONDS
workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;SynchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。PriorityBlockingQuene:具有优先级的无界阻塞队列
threadFactory:线程工厂,主要用来创建线程handler:表示当拒绝处理任务时的策略,有以下四种取值:ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
线程池状态
ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:
volatile int runState;static final int RUNNING = -1;当创建线程池后,初始时,线程池处于RUNNING状态;static final int SHUTDOWN = 0;如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕static final int STOP = 1;如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;static final int TIDYING = 2;该状态表示线程池对线程进行整理优化;static final int TERMINATED = 3;当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态
runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;
Executors类
不过在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:
Executors.newCachedThreadPool();//创建一个可缓存线程池,缓冲池容量大小为Integer.MAX_VALUEExecutors.newSingleThreadExecutor();//创建容量为1的缓冲池,即线程池中每次只有一个线程工作,单线程串行执行任务,Executors.newFixedThreadPool(int);//创建固定容量大小的缓冲池,固定数量的线程池,没提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行Executors.newScheduleThreadExecutor();// 大小无限制的线程池,能按时间计划来执行任务,允许用户设定计划执行任务的时间。在实际的业务场景中可以使用该线程池定期的同步数据。
这几个个方法的具体实现:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
从它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了:
newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue;newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。newScheduledThreadPool:maximumPoolSize``Integer.MAX_VALUE使用DelayedWorkQueue())。
实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。
向线程池提交任务
有两种方式:
Executor.execute(Runnable command);ExecutorService.submit(Callable<T> task);
execute()内部实现
- 首次通过
workCountof()获知当前线程池中的线程数,如果小于corePoolSize, 就通过addWorker()创建线程并执行该任务;否则,将该任务放入阻塞队列; - 如果能成功将任务放入阻塞队列中,如果当前线程池是非RUNNING状态,则将该任务从阻塞队列中移除,然后执行
reject()处理该任务;如果当前线程池处于RUNNING状态,则需要再次检查线程池(因为可能在上次检查后,有线程资源被释放),是否有空闲的线程;如果有则执行该任务; - 如果不能将任务放入阻塞队列中,说明阻塞队列已满;那么将通过
addWoker()尝试创建一个新的线程去执行这个任务;如果addWoker()执行失败,说明线程池中线程数达到maxPoolSize,则执行reject()处理任务;
submit()内部实现
会将提交的Callable任务会被封装成了一个FutureTask对象,FutureTask类实现了Runnable接口,这样就可以通过Executor.execute()提交FutureTask到线程池中等待被执行,最终执行的是FutureTask的run方法;
比较:
两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。
线程池的关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭:
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
你的star是我的动力!!!
1139

被折叠的 条评论
为什么被折叠?



