- 过多的线程会耗尽CPU和内存资源,虽然与进程相比,线程是一种轻量级的工具,但是其创建和关闭依然需要花费时间。
- 线程本身会占用内存空间,大量的线程会抢占宝贵的内存资源。大量的线程回收也会给GC带来压力,延长GC的停顿时间。
- 线程池可以让线程复用,可以节约不少创建和销毁线程的时间。
Executor 框架
ThreadPoolExecutor:表示线程池。
Executors:扮演线程池工厂的角色,通过Executors 可以取得拥有特定功能的线程池。
public static ExecutorService newFixedThreadPool(int nThreads)
返回固定线程数量的线程池。线程池中的数量始终不变,若没有空闲的线程,则新的线程会被暂存在一个额任务列表中。
public static ExecutorService newSingleThreadExecutor()
返回只有一个线程的线程池,多余的任务被暂存到任务列表中,按先进先出的顺序执行。
public static ExecutorService newCachedThreadPool()
- 该线程池可以显著提高大量短暂的异步任务的性能(many short-lived asynchronous tasks)。
- 如果没有可用的线程,则新建线程加入线程池。
- 如果空闲60秒,会被移除线程池。
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
返回一个ScheduledExecutorService对象,线程池大小为1。ScheduledExecutorService 接口在ExecutorServiec之上扩展了在给定时间执行某任务的功能,如在某个固定的延时之后执行,或者周期性执行某个任务。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
也返回ScheduledExecutorService 对象, 但该线程可以指定线程数量。
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);
long initialDelay: 第一次执行的延迟时间。
long delay: 上一个任务结束后延迟执行下一个任务的时间。
- schedule()会在给定的时间,对任务进行一次调度。
- scheduleFixedRate() 任务频率是一定的,是以上一个任务开始执行时间为起点,调度下一次任务,如果任务的执行时间超过了调度时间,下个任务会立即被调用。
- scheduleWithFixedDelay() 是在上一个任务结束时间为起点,经过delay时间进行任务调度。
遇到异常,那么后续的所有任务都会停止调度,因此,必须保证异常的及时处理,为周期性的任务的稳定调度提供条件。
线程池的内部实现:
上述的几种线程池有着不同的功能特点,但其内部实现都是使用ThreadPoolExecutor实现。
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
- corePoolSize:线程池中的线程数量。
- maximumPoolSize:线程池中的最大线程数量。
- keepAliveTime:当线程数超过corePoolSize时,多余的空闲线程的存活时间。
- unit:keepAliveTime的时间单位。
- workQueue:任务队列,被提交但尚未被执行的任务(等待有空闲的线程)。
- threadFactory: 线程工厂,用于线程的创建,一般用默认的即可。
- handler:拒绝策略。当任务太多来不及处理,如何拒绝任务。
任务队列:workQueue
workQueue是一个BlockingQueue 接口的对象,仅用于存放Runnable对象。
- SynchronousQueue:直接提交的队列,没有容量!每一个插入操作都要等待一个相应的删除操作,反之,每一个删除操作都要等待对应的插入操作。提交的任务不会被真实的保存,而总是将新任务提交给线程执行,如果没有空闲的线程,则尝试创建线程,如果线程数量已经达到最大值,则执行拒绝策略。
- ArrayBlockingQueue:有界的任务队列。构造参数必须带一个容量参数。如果线程池的实际线程小于corePoolSize,则优先创建线程,若大于corePoolSoize,则会将新任务加入等待队列。若等待队列已满,则在线程池总线程数不大于maximumPoolSize的前提下,创建新线程,否则执行拒绝策略。
- LinkedBlockingQueue:无界的任务队列。线程数小于corePoolSize,创建新的线程执行。当达到corePoolSize后,不会继续增加,若后续的任务加入,且没有空闲的线程,则任务直接加入队列等待。若加入任务和处理任务的差异很大,队列会快速增长,直到内存耗尽。
- PriorityBlockingQueue:优先任务队列,是一个特殊的无界队列。总是确保高优先级的任务先执行。
拒绝策略
- AbortPolicy:直接抛出异常,阻止系统正常工作。
- CallerRunsPolicy:只要线程池未关闭,任务直接在调用这线程中执行,不会丢弃任务,但是,提交任务的线程的性能可能急剧下降。
- DiscardOledestPolicy:丢弃最老的请求,并尝试再次提交当前任务。
- DiscardPolicy:默默地丢弃无法处理的任务。
自定义线程创建:ThreadFactory
线程池中的线程都是通过 接口 ThreadFactor接口的newThread()创建的。默认实现为Executors.DefaultThreadFactory。
通过自定义线程池,可以跟踪线程在何时创建了多少线程,也可以自定义线程的名称、组以及优先级等信息。
扩展线程池
通过扩展ThreadPoolExecutor,重写其 beforeExecute()、afterExecute()、terminated()三个接口,可以监控任务执行的开始和结束时间,或者其他一些自定义的增强功能。
合理的线程池线程数量
线程池的大小对系统的性能有一定的影响。过大或过下的线程数量无法发挥最优的系统性能,但是线程池大小的确认也不需要做到非常精确,只要避免极大和极小两种情况。
Nthreads = Ncpu * Ucpu * ( 1 + W / C )
- Ncpu = CPU的数量
- Ucpu = 目标CPU的使用率
- W/C = 等待时间/计算时间的比率 ???
线程异常处理
可通过重写ThreadPoolExecutor的executre() 方法,在异常捕获中打打印任务提交线程的堆栈信息。
class TraceThreadPoolExecutor extends ThreadPoolExecutor{
public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
public void execute(Runnable command) {
super.execute(exceptionWrap(command, new Exception(""),Thread.currentThread().getName()));
}
@Override
public Future<?> submit(Runnable task) {
return super.submit(exceptionWrap(task, new Exception(""),Thread.currentThread().getName()));
}
}
private Runnable exceptionWrap(final Runnable task, final Exception invokerStack,String invokerThreadName){
return new Runnable() {
@Override
public void run() {
try {
task.run();
}catch ( Exception e){
invokerStack.printStackTrace(); // 打印调用这堆栈信息
throw e;
}
}
};
}