目录
1.newFixedThreadPool(固定大小的线程池)
绪论
为什么使用线程池?
其实,这个问题可以反过来思考一下,不使用线程池会怎么样?当需要多线程并发执行任务时,只能不断的通过new Thread创建线程,每创建一个线程都需要在堆上分配内存空间,同时需要分配虚拟机栈、本地方法栈、程序计数器等线程私有的内存空间,当这个线程对象被可达性分析算法标记为不可用时被GC回收,这样频繁的创建和回收需要大量的额外开销。再者说,JVM的内存资源是有限的,如果系统中大量的创建线程对象,JVM很可能直接抛出OutOfMemoryError异常,还有大量的线程去竞争CPU会产生其他的性能开销,更多的线程反而会降低性能,所以必须要限制线程数。
既然不使用线程池有那么多问题,我们来看一下使用线程池有哪些好处:
- 使用线程池可以复用池中的线程,不需要每次都创建新线程,减少创建和销毁线程的开销;
- 同时,线程池具有队列缓冲策略、拒绝机制和动态管理线程个数,特定的线程池还具有定时执行、周期执行功能,比较重要的一点是线程池可实现线程环境的隔离,例如分别定义支付功能相关线程池和优惠券功能相关线程池,当其中一个运行有问题时不会影响另一个。
一.Java线程池之间的继承关系
其中ExecutorService是底层的接口,而ScheduledExecutorService则扩展了一些方法,使得其实现ScheduledThreadPoolExecutor(任务调度线程池)可以让线程延迟执行任务。
二.线程池的状态
ThreadPoolExxcutor使用int的高3位来表示线程池的状态,低29位表示线程数量
细节:为什么要用一个int来同时保存线程池的状态和线程数量呢?
答:因为线程池的状态和其线程的数量同时保存在一个原子变量,这样就可以用一次cas原子操作就可以为其赋值,保证了原子性。
三.线程池的构造方法(参数)
线程池的构造方法参数有:
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize:核心线程数,最多保留的线程数
- maximumPoolSize:最大线程数目(核心线程数+急救线程数)
- keepAliveTime:急救线程的生存时间
- unit:生存时间的时间单位
- workQueue:阻塞队列(当线程数等于核心线程数,则会将任务加入阻塞队列。当阻塞队列也满了,则会申请救急线程来执行多出来的任务)
- threadFactory:线程工厂(主要是对线程的创建进行一些定义,如命名)
- handler:拒绝策略(当线程数等于最大线程数,此时还需要额外的线程时,则会触发拒绝策略。常见的拒绝策略有:调用者抛出RejectedExecutionException异常、调用者执行本次任务、放弃本次任务、放弃队列中最早的任务,本任务取而代之、Netty则会创建一个新线程来执行)
四.线程池的运行过程(工作原理)
1.当线程数<核心线程数时
此时线程池会继续创建线程来执行任务
2.当线程数=核心线程数且阻塞队列未满时
此时线程池会将待执行的任务放入阻塞队列中,直到核心线程空闲后,从队列中取出任务执行。
3.当线程数=核心线程数且阻塞队列满了时
此时将会创建不超过maximumPoolSize-corePoolSize数目的救急线程来执行多出的任务(有点插队的感觉,堵在阻塞队列外面的反而不用排队,直接由救急线程处理)。
4.当线程数=最大线程数
此时如果有新的任务需要执行,则会执行拒绝策略。
五.线程池的种类
JDK Executors类中提供了众多工厂方法来创建各种用途的线程池
1.newFixedThreadPool(固定大小的线程池)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
特点
- 核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间
- 阻塞队列是无界的,可以放任意数量的任务
- 适用于任务量已知,相对耗时的任务
2.newCachedThreadPool(缓存线程池)
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(
0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
特点
- 核心线程数是 0, 最大线程数是 Integer.MAX_VALUE,救急线程的空闲生存时间是 60s,意味着
- 全部都是救急线程(60s 后可以回收)
- 救急线程可以无限创建(容易造成内存溢出)
- 队列采用了 SynchronousQueue 实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱、一手交货)
- 适合任务数比较密集,但每个任务执行时间较短的情况
3.newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(
new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
使用场景:
希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程 也不会被释放。
区别:
自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一 个线程,保证池的正常工作。
Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改 FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法
Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改 对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改
细节:
newSingleThreadExecutor中的阻塞队列是无界限的队列
六.线程池中的一些操作方法
1.提交方法
// 执行任务
void execute(Runnable command);
// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);
// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
2.关闭线程池
/*
线程池状态变为 SHUTDOWN
- 不会接收新任务
- 但已提交任务会执行完
- 此方法不会阻塞调用线程的执行
*/
void shutdown();
/*
线程池状态变为 STOP
- 不会接收新任务
- 会将队列中的任务返回
- 并用 interrupt 的方式中断正在执行的任务
*/
List<Runnable> shutdownNow()
// 不在 RUNNING 状态的线程池,此方法就返回 true
boolean isShutdown();
// 线程池状态是否是 TERMINATED
boolean isTerminated();
// 调用 shutdown 后,由于调用线程并不会等待所有任务运行结束,因此如果它想在线程池 TERMINATED 后做些事情,可以利用此方法等待
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;