相关源码:boy-learning-thread
个人博客:http://bruce.bugmakers.club
内容来自《网易微专业 - 高性能编程章节》
线程池原理
1、为什么要用线程池
线程是不是越多越好?
1、线程在 java 中是一个对象,更是操作系统中的资源,线程创建、销毁需要时间。如果 “创建时间 + 销毁时间 > 执行任务时间” 就恨不合算。
2、java 对象占用堆内存,操作系统线程占用系统内存,根据 jvm 规范,一个线程默认最大栈大小 1M,这个空间是需要从系统内存中分配的。线程过多,会消耗很多内存。
3、操作系统需要频繁切换线程上下文(大家都想被执行),线程过多会影响性能。
线程池的推出,就是为了方便的控制线程数量。
2、线程池原理 - 概念
1、线程池管理器:用于创建并管理线程池,包括:创建线程池、销毁线程池、添加新任务;
2、工作线程:线程池中的线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口:每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了:任务的入口、任务执行完后的收尾工作、任务的状态;
4、任务队列:用于存放没有处理的任务。以供一种缓冲机制。
2.1、线程池 API - 接口定义和实现类
类型 | 名称 | 描述 |
---|---|---|
接口 | Executor | 最上层接口,定义了执行任务的方法 execute |
接口 | ExecutorService | 继承了 Executor 接口,拓展了 Callable、Future、关闭方法 |
接口 | ScheduledExecutorService | 继承了 ExecutorService,增加了定时任务相关的方法 |
实现类 | ThreadPoolExecutor | 基础、标准的线程池实现类 |
实现类 | ScheduledThreadPoolExecutor | 继承了 ThreadPoolExecutor,实现了 ScheduledExecutorService 中相关定时任务的方法 |
可以认为 ScheduledThreadPoolExecutor 是最丰富的实现类。
2.2、线程池 API - 方法定义
ExecutorService
// 监测 ExecutorService 是否已关闭,直到所有任务完成执行,或超时发生,或当前线程被中断
awaitTermination(long timeout, TimeUnit unit);
// 执行给定的任务集合,执行完毕后,返回结果
invokeAll(Collection extends Callable> tasks);
// 执行给定的任务集合,执行完毕或者超时后,返回结果,其他任务终止
invokeAll(Collection extends Callable> tasks, long timeout, TimeUnit unit);
// 执行给定的任务集合,任意一个任务执行成功后,返回结果,其他任务终止
invokeAny(Collection extends Callable> tasks);
// 执行给定的任务集合,任意一个任务执行成功或超时后,返回结果,其他任务终止
invokeAny(Collection extends Callable> tasks, long timeout, TimeUnit unit);
// 如果线程池关闭,则返回treu
isShutdown();
// 如果关闭后,所有任务都已经执行完毕,则返回true
isTerminated();
// 优雅关闭线程池,之前已经提交的任务将被执行,但不接受新的任务
shutdown();
// 尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行任务的列表
shutdownNow();
// 提交一个用于执行的 Callable 返回任务,并返回一个 Future 对象,用于获取 Callable 执行结果
submit(Callable task);
// 提交可运行任务以执行,并返回一个 Future 对象,执行结果为 null
submit(Runnable task);
// 提交可运行任务以执行,并返回一个 Future 对象,执行结果为传入的 result
sumit(Runnable task, T result);
ScheduledExecutorService
// 创建并执行一个一次性任务,过了延迟时间就会被执行
schedule(Callable callable, long delay, TimeUnit unit);
schedule(Runnable command, long delay, TimeUnit unit);
// 创建并执行一个周期性任务,过了延迟时间会第一次执行,执行过程发生异常,那么任务就停止了
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
scheduleWithFixedDelay(Runnable command, long initialDelay, long period, TimeUnit unit);
scheduleAtFixedRate 方法,一次任务执行时长超过周期时间,下次任务在该次任务执行完之后,立即执行。
schedualWithFixedDelay 方法,一次任务执行时间长超过周期时间,下次任务会在该次任务执行完之后,计算执行延时(再等延迟一个 period 的时长)。
2.3、线程池 API - Executors 工具类
你也可以自己实例化线程池,也可以用 Executors 创建线程池的工厂类,常用方法如下:
1) newFixedThreadPool(int nThreads)
创建一个固定大小、任务队列无界的线程池。核心线程数 = 最大线程数。
2) newCachedThreadPool()
创建一个大小无界的缓存线程池。他的任务队列是一个同步队列。
任务加入到池中,如果池中有空闲线程,则用空闲线程执行,如无则创建新的线程执行,池中的线程空闲超过60秒,将被销毁释放。
线程数随任务的多少变化。适用于任务量不可控,且执行耗时较小的异步任务。
核心线程数 = 0, 最大线程数 = Integer.MAX_VALUE
3) newSingleThreadExecutor()
只有一个线程来执行无界任务队列的单一线程池。该线程池确保任务按照加入队列的顺序一个一个一次执行。
当唯一的线程因任务异常终止时,将创建一个新的线程来执行后续的任务。
与 newFixedThreadPool(1) 的区别在于,单一线程池的大小在 new SingleThreadExecutor 方法中硬编码,不能再改变。
4) newScheduledThreadPool(int corePoolSize)
能定时执行任务的线程池。该池的核心线程数由参数来指定,最大线程数 = Integer.MAX_VALUE
3、线程池原理 - 任务 execute 过程
1、是否达到核心线程数量?没达到,创建一个工作线程来执行任务。
2、任务队列是否已满?没满,则将新提交的任务存储在任务队列里边。
3、是否达到线程池最大数量?没达到,则创建一个新的工作线程来执行任务。
4、最后,执行拒绝策略来处理这个任务。
4、线程数量
如何确定合适数量的线程?
计算型任务:cpu数量的1~2倍。
IO型任务:根据具体的IO阻塞时长进行考量决定。
如 Tomcat 中默认的最大线程数为:200
也可以考虑根据需要在一个最小数量和最大数量间自动增减线程数。
监控CPU的使用率,如果远小于80%则使用不合理,大于也不可理,接近则表示使用良好。