boy-learning-thread | 1.1.6 线程池原理

相关源码: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<T>> tasks);

// 执行给定的任务集合,执行完毕或者超时后,返回结果,其他任务终止
invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit);

// 执行给定的任务集合,任意一个任务执行成功后,返回结果,其他任务终止
invokeAny(Collection<? extends Callable<T>> tasks);

// 执行给定的任务集合,任意一个任务执行成功或超时后,返回结果,其他任务终止
invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit);

// 如果线程池关闭,则返回treu
isShutdown();

// 如果关闭后,所有任务都已经执行完毕,则返回true
isTerminated();

// 优雅关闭线程池,之前已经提交的任务将被执行,但不接受新的任务
shutdown();

// 尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行任务的列表
shutdownNow();

// 提交一个用于执行的 Callable 返回任务,并返回一个 Future 对象,用于获取 Callable 执行结果
submit(Callable<T> task);

// 提交可运行任务以执行,并返回一个 Future 对象,执行结果为 null
submit(Runnable task);

// 提交可运行任务以执行,并返回一个 Future 对象,执行结果为传入的 result
sumit(Runnable task, T result);

ScheduledExecutorService

// 创建并执行一个一次性任务,过了延迟时间就会被执行
schedule(Callable<T> 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%则使用不合理,大于也不可理,接近则表示使用良好。

推荐阅读更多精彩内容