1、线程池好处:
降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度:当任务到达时,可以不需要等待线程创建就能立即执行。
提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,监控和调优。
2、相关类
2.1 Executor
Executor接口是异步任务执行框架的基础,该框架能够支持多种不同类型的任务执行策略。
Executor接口就提供了一个执行方法,任务是Runnbale类型,不支持 Callable类型。
public interface Executor {
void execute(Runnable command);
}
2.2 ExecutorService
ExecutorService接口实现了Executor接口,主要提供了关闭线程池和 submit方法:
public interface ExecutorService extends Executor {
// 该方法会停止ExecutorService添加新的任务, 但是老任务还是会继续执行.
// 这句话指的是该方法会立即返回, 但不一定代表之前提交的任务已经全部完成了.
// 如果需要一个阻塞的方法, 可以调用awaitTermination方法.
// 该方法内部实现是设置了状态, 并interrupt了所有的空闲线程, 使其不再接受新的任务.
void shutdown();
// 该方法尝试停止所有正在执行的任务, 停止对正在等待执行的任务的处理, 并且返回正在等待执行的任务.
// 同shutdown(), 该方法也是立刻返回的, 不会等到所有任务终止以后才返回.
// 因为终止是通过interrupt实现的, 所以如果那个任务没有对interrupt做出正确响应,
// 那么该方法将无法终止该任务. 所以传进去的任务需要对interrup做出合适的响应.
List<Runnable> shutdownNow();
boolean isShutdown();
// 该方法是阻塞的, 阻塞到所有任务都完成(必须在shutdown调用之后)或者超时.
// 如果executor在超时之前终止了, 那么返回true, 否则返回false.
// 注意, 如果不在awaitTermination前调用shutdown, 则即使在超时之前所有任务都已经完成,
// awaitTermination仍然会等待着, 而且最后一定返回false,
// 因为没有shutdown的调用不会使executor的状态变为terminated.
boolean isTerminated();
// 提交一个返回值的任务以供执行,并返回一个表示该任务待处理结果的 Future。
// Future 的get方法将在成功完成后返回任务的结果。
// 如果您想立即阻止等待任务,可以使用result = exec.submit(aCallable).get();形式的结构
<T> Future<T> submit(Callable<T> task);
// 提交 Runnable 任务以执行并返回代表该任务的 Future。 Future 的get方法将在成功完成后返回给定的结果。
<T> Future<T> submit(Runnable task, T result);
// 提交 Runnable 任务以执行并返回代表该任务的 Future。 Future 的get方法将在成功完成后返回null
Future<?> submit(Runnable task);
}
2.3 ThreadPoolExecutor/ScheduledThreadPoolExecutor
ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务
ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行任务或者定期执行命令
public ThreadPoolExecutor(int corePoolSize, // 线程池中核心线程的数量
int maximumPoolSize,// 线程池中最大线程数量
long keepAliveTime, // 非核心线程的超时时长,当系统中非核心线程闲置时间。超过keepAliveTime
// 之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut
// 属性设置为true,则该参数也表示核心线程的超时时长
TimeUnit unit, // 第三个参数的单位,有纳秒、微秒、毫秒、秒、分、时、天等
BlockingQueue<Runnable> workQueue, // 线程池中的任务队列,该队列主要用来存储已经被提交但是
// 尚未执行的任务。存储在这里的任务是由
// ThreadPoolExecutor的execute 方法提交来的。
ThreadFactory threadFactory, // 为线程池提供创建新线程的功能,这个我们一般使用默认即可
RejectedExecutionHandler handler) { // 拒绝策略,当线程无法执行新任务时(一般是由于线程池中
// 的线程数量已经达到最大数或者线程池关闭导致的),
// 默认情况下,当线程池无法处理新线程时,
// 会抛出一个RejectedExecutionException。
提交一个任务到线程池中,线程池的处理流程:
(1) 当当前线程数小于核心线程数时,直接启动一个核心线程并执行任务;
(2) 当当前线程数大于核心线程数、并且任务队列未满时,添加进来的任务会被安排到任务队列中等待执行;
(3) 当任务队列已满,但是当前线程数小于最大线程数时,会立即开启一个非核心线程来执行任务;
(4) 当前线程数大于核心线程、任务队列已满、并且当前线程数大于最大线程数时,则会触发拒绝策略。一共有四种拒绝策略,默认抛出 RejectExecutionExpection异常。
线程池大小计算方式:
CPU密集型任务(N+1):这种任务消耗的主要是CPU资源,可以将线程数设置为N(CPU核心数)+1,比CPU核心数多出来一个线程是为了 防止线程偶发的缺页中断,或者其他原因导致的任务暂停而带来的影 响。一旦任务停止,CPU就会出于空闲状态,而这种情况下多出来一个线程就可以充分利用CPU的空闲时间。
I/O密集型(2N):这种任务应用起来,系统大部分时间用来处理I/O交互,而线程在处理I/O的是时间段内不会占用CPU来处理,这时就可以将CPU交出给其他线程使用。因此在I/O密集型任务的应用中,可以配置多一些线程,具体计算方是2N。
四种拒绝策略:
(1) AbortPolicy:直接抛出异常。默认策略
(2) CallerRunsPolicy:只用调用所在的线程运行任务
(3) DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
(4) DiscardPolicy:不处理,直接丢弃
2.4 Executors
java给我们提供了Executors去创建线程池。但是阿里发布的 Java开发手册中不允许使用 Executors 去创建, 而是通过 ThreadPoolExecutor 的方式。这是为什么呢?我们先来了解下Executors是怎么创建线程池的。
(1) FixedThreadPool:
有固定数量线程的线程池。其核心线程数等于最大线程数,且闲置回收时间为0,适合线程稳定的场所。
(2) SingleThreadPool:
有固定数量线程的线程池,且数量为一。从数学的角度来看 SingleThreadPool应该属于FixedThreadPool的子集。其核心线程数、最大线程数均为1,且闲置回收时间为0,适合线程同步操作的场所。
(3) CachedThreadPool:
容量很大的线程池,核心线程数为0, 最大线程数为Integer.MAX_VALUE(2^32-1一个很大的数字)。
(4) ScheduledThreadPool:
最大线程数为Integer.MAX_VALUE,具有定时定期执行任务功能的线程池。
2.5 Callable、Future、FutureTask详解
Callable与Future是在JAVA的后续版本中引入进来的,Callable类似于 Runnable接口,实现Callable接口的类与实现Runnable的类都是可以被线程执行的任务。
三者之间的关系: Callable是Runnable封装的异步运算任务。 Future用来保存Callable异步运算的结果FutureTask封装Future的实体类
1.Callable
public interface Callable<V> {
/**
* 计算结果,如果不能这样做,则抛出异常
*/
V call() throws Exception;
}
与Runnbale的区别
a、Callable定义的方法是call,而Runnable定义的方法是run。
b、call方法有返回值,而run方法是没有返回值的。
c、call方法可以抛出异常,而run方法不能抛出异常。
2.Future
Future表示异步计算的结果,提供了以下方法,主要是判断任务是否完成、中断任务、获取任务执行结果
public interface Future<V> {
/**
* 尝试取消此任务的执行。如果任务已完成、已被取消、或由于其他原因无法取消,则此尝试将失败。
* 如果成功,并且在调用 cancel 时此任务尚未启动,此任务不应该运行。
* 如果任务已经开始,那么 mayInterruptIfRunning 参数确定执行此任务的线程是否应该被中断尝试停止任务。
* 此方法返回后,对 isDone 的后续调用将始终返回true。
* 如果此方法返回true,则对 isCancelled 的后续调用将始终返回true。
* Params mayInterruptIfRunning true,如果执行此任务的线程应该被中断;false,允许完成正在进行的任务;
* @return 如果任务无法取消,则返回 false,通常是因为它已经正常完成;否则为真
*/
boolean cancel(boolean mayInterruptIfRunning);
/**
* 如果此任务在正常完成之前被取消,则返回 true
*/
boolean isCancelled();
/**
* 如果此任务完成,则返回 true。完成可能是由于正常终止、异常或取消——在所有这些情况下,此方法都将返回 true。
*/
boolean isDone();
/**
* 有必要,等待计算完成,然后检索其结果。
*/
V get() throws InterruptedException, ExecutionException;
/**
* 如有必要,最多等待给定时间以完成计算,然后检索其结果(如果可用)
* @param 最长等待时间
* @param timeout参数的时间单位
*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
3.FutureTask
可取消的异步计算,此类提供了对Future的基本实现,仅在计算完成时才能获取结果,如果计算尚未完成,则阻塞get方法。
public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V>
FutureTask不仅实现了Future接口,还实现了Runnable接口,所以不仅可以将FutureTask当成一个任务交给Executor来执行,还可以通过 Thread来创建一个线程。
3 为什么不推荐使用 Executors 去创建线程池?
3.1 内存占用
从上面可知, Executors可以大致可以通过四种方式进行创建线程池。
FixedThreadPool、SingleThreadPool都有固定数量线程的线程池,但核心线程数与最大线程数相等。任务队列的大小没有设置,是默认的,也就是无界的。所以有可能会导致其无限增大,最终内存撑爆。
CachedThreadPool核心线程数是0,拥有最大的执行线程数,但是默认的任务队列中只能存一个,可以认为所有放到 newCachedThreadPool() 中的线程,不会缓存到队列中,而是直接运行的。随着执行线程数量的增多和线程没有及时结束,最终会将内存撑爆。
ScheduledThreadPool可以创建固定数量的核心线程,可以延迟或定时的执行任务。但最大执行线程数也是无限的,同样会导致内存撑满。
3.2 拒绝策略不能自定义
Executors 底层也是通过 ThreadPoolExecutor 创建的,但 ThreadPoolExecutor 的默认策略,即 AbortPolicy。当线程无法执行新任务时,一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的,会直接抛出异常。