目录
线程池
介绍了为什么要使用线程和线程池:JAVA中线程池的定义及使用
Java线程池ExecutorService
有介绍Future接口、ScheduledExecutorService接口:秒懂 Java ExecutorService
作用和目的
为什么要使用线程池技术?
- 降低系统资源消耗, 通过重用已存在的线程, 降低线程创建和销毁造成的消耗;
- 提高系统响应速度, 当有任务到达时, 无需等待新线程的创建便能立即执行;
方便线程并发数的管控, 线程若是无限制的创建, 不仅会额外消耗大量系统资
源, 更是占用过多资源而阻塞系统或oom等状况, 从而降低系统的稳定性。 线程池 - 能有效管控线程, 统一分配、 调优, 提供资源使用率;
- 更强大的功能, 线程池提供了定时、 定期以及可控线程数等功能的线程池, 使用
方便简单
Executor框架
框架简介,接口和类间继承关系图
线程池的创建
1. 手动创建
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler){}
ThreadPoolExecutors()参数含义:
-
corePoolSize:核心线程数,一旦创建将不会再释放。如果创建的线程数还没有达到指定的核心线程数量,将会继续创建新的核心线程,直到达到最大核心线程数后,核心线程数将不在增加;如果没有空闲的核心线程,同时又未达到最大线程数,则将继续创建非核心线程;如果核心线程数等于最大线程数,则当核心线程都处于激活状态时,任务将被挂起,等待空闲线程来执行。默认情况下, 核心线程一直存活在线程池中, 即便他们在线程池中处于闲置状态。 除非我们将ThreadPoolExecutor的allowCoreThreadTimeOut属性设为true的时候, 这时候处于闲置的核心线程在等待新任务到来时会有超时策略, 这个超时时间由keepAliveTime来指定。 一旦超过所设置的超时时间, 闲置的核心线程就会被终止。
-
maximumPoolSize:最大线程数,允许创建的最大线程数量。如果最大线程数等于核心线程数,则无法创建非核心线程;如果非核心线程处于空闲时,超过设置的空闲时间,则将被回收,释放占用的资源。
keepAliveTime: 也就是当线程空闲时,所允许保存的最大时间,超过这个时间,线程将被释放销毁,但只针对于非核心线程。
TimeUnit:时间单位,用于指定keepAliveTime的时间单位。 -
workQueue:等待队列,用于存储等待处理的任务。线程池提供了多种类型的等待队列,例如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue等。
通过线程池中的execute方法提交的Runable对象都会存储在该队列中。
- ArrayBlockingQueue 基于数组实现的有界的阻塞队列,该队列按照FIFO( 先进先出) 原则对队列中的元素进行排序。
- LinkedBlockingQueue 基于链表实现的有界的阻塞队列,该队列按照FIFO( 先进先出) 原则对队列中的元素进行排序。
- SynchronousQueue 内部没有任何容量的阻塞队列。 在它内部没有任何的缓存空间。 对于SynchronousQueue中的数据元素只有当我们试着取走的时候才可能存在。
- PriorityBlockingQueue 具有优先级的无限阻塞队列。
-
threadFactory: 线程工厂, 为线程池提供新线程的创建。 ThreadFactory是一个接口, 里面只有一个newThread方法。 默认为DefaultThreadFactory类。
-
handler:拒绝策略,用于处理无法处理的任务。线程池提供了多种类型的拒绝策略,例如AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy等。是RejectedExecutionHandler对象, 而RejectedExecutionHandler是一个接口, 里面只有一个rejectedExecution方法,当任务队列已满并且线程池中的活动线程已经达到所限定的最大值或者是无法成功执行任务, 这时候ThreadPoolExecutor会调用RejectedExecutionHandler中的rejectedExecution方法。在ThreadPoolExecutor中有四个内部类实现了rejectedExecutionHandler接口。 在线程池中它默认是AbortPolicy, 在无法处理新任务时抛出RejectedExecutionException异常。
- CallerRunsPolicy 直接用调用者所在线程来运行任务。
- AbortPolicy 直接抛出RejectedExecutionException异常。
- DiscardPolicy 丢弃掉该任务,不进行处理。
- DiscardOldestPolicy 丢弃队列里最近的一个任务,并执行当前任务。
我们也可以通过实现RejectedExecutionHandler接口来自定义我们自己的handler。如记录日志或持久化不能处理的任务。
2. 使用Executors工厂类方法创建
Executors只是一个工厂类,它所有的方法返回的都是ThreadPoolExecutor、ScheduledThreadPoolExecutor这两个类的实例
- Executors.newCachedThreadPool():
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。线程的空闲等待时间是60s,超过此时间将会被回收。
对于newCachedThreadPool他的任务队列采用的是SynchronousQueue, 上面说到在SynchronousQueue内部没有任何容量的阻塞队列。 SynchronousQueue内部相当于一个空集合, 我们无法将一个任务插入到SynchronousQueue中。 所以说在线程池中如果现有线程无法接收任务,将会创建新的线程来执行任务。
- Executors.newFixedThreadPool:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- Executors.newScheduledThreadPool:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
创建一个定长线程池,支持定时及周期性任务执行。它的核心线程数是固定的, 对于非核心线程几乎可以说是没有限制的, 并且当非核心线程处于限制状态的时候就会立即被回收。
- Executors.newSingleThreadExecutor:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
任务提交
-
execute(Runnable)方法:没有办法获知task的执行结果。
-
submit(Runnable)方法:submit(Runnable)和execute(Runnable)区别是前者可以返回一个Future对象,通过返回的Future对象,我们可以检查提交的任务是否执行完毕。当执行完毕时,通过返回的future.get()方法可以获得一个null,没执行完毕时会阻塞get()方法。
-
submit(Callable)方法:submit(Callable)和submit(Runnable)类似,也会返回一个Future对象,但是除此之外,submit(Callable)接收的是一个Callable的实现,Callable接口中的call()方法有一个返回值,可以返回任务的执行结果。
-
T result = invokeAny(Collection<? extents Callable>)方法:
public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException {
try {
return doInvokeAny(tasks, false, 0);
} catch (TimeoutException cannotHappen) {
assert false;
return null;
}
}
invokeAny(…)方法接收的是一个Callable的集合,执行这个方法不会返回Future,但是会返回所有Callable任务中其中一个任务的执行结果。这个方法也无法保证返回的是哪个任务的执行结果,反正是其中的某一个。
- List<Future> = invokeAll(Collection<? extents Callable>)方法:
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
}
invokeAll(…)与 invokeAny(…)类似也是接收一个Callable集合,但是前者执行之后会返回一个Future的List,其中对应着每个Callable任务执行后的Future对象。
线程池执行流程
- 如果在线程池中的线程数量没有达到核心的线程数量, 这时候就回启动一个核心线程来执行任务。
- 如果线程池中的线程数量已经超过核心线程数, 这时候任务就会被插入到任务队列中排队等待执行。
- 由于任务队列已满, 无法将任务插入到任务队列中。 这个时候如果线程池中的线程数量没有达到线程池所设定的最大值, 那么这时候就会立即启动一个非核心线程来执行任务。
- 如果线程池中的数量达到了所规定的最大值, 那么就会拒绝执行此任务, 这时候就会调用RejectedExecutionHandler中的rejectedExecution方法来通知调用者。
注意:当核心线程任务满时,新来的任务先采用任务队列缓存,当任务队列满时才会新建非核心线程。
线程池的关闭
为什么需要关闭线程池?
如果的应用程序是通过main()方法启动的,在这个main()退出之后,如果应用程序中的ExecutorService没有关闭,这个应用将一直运行。之所以会出现这种情况,是因为ExecutorService中运行的线程会阻止JVM关闭。
-
public void shutdown()方法
在调用shutdown()方法之后,将线程池状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
在调用shutdown()方法之后,ExecutorService不会立即关闭,但是它不再接收新的任务,直到当前所有线程执行完成才会关闭,所有在shutdown()执行之前提交的任务都会被执行。 -
public List shutdownNow()方法
将线程池的状态设置成STOP状态,然后中断所有任务(包括正在执行的)的线程,并返回等待执行任务的列表。
这个动作将跳过所有正在执行的任务和被提交还没有执行的任务。但是它并不对正在执行的任务做任何保证,有可能它们都会停止,也有可能执行完成。
shutdownNow() 方法会尝试立即销毁 ExecutorService 实例,所以并不能保证所有正在运行的线程将同时停止。
该方法会返回等待处理的任务列表,由开发人员自行决定如何处理这些任务。
因为提供了两个方法,因此关闭 ExecutorService 实例的最佳实战 ( 也是 Oracle 所推荐的 )就是同时使用这两种方法并结合 awaitTermination() 方法。
使用这种方式,ExecutorService 首先停止执行新任务,等待指定的时间段完成所有任务。如果该时间到期,则立即停止执行。
executorService.shutdown();
try {
if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
}
ScheduledThreadPoolExecutor介绍
用于执行定时性、或者周期性的任务
- 定时执行Runnable接口
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit){}
在固定延迟后安排单个任务的执行,返回的Future对象可以用作判断任务是否执行完成。
- 定时执行Callable接口
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay,
TimeUnit unit) {}
在固定延迟后安排单个任务的执行,返回的Future对象可以用作判断任务是否执行完成和获取任务执行的结果。
- 执行周期性任务接口
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay, // 任务将在初始延迟之后第一次执行
long period, // 线程池将在第一次执行任务后每隔此时间执行一次相同的任务
TimeUnit unit) {}
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay, // 任务将在初始延迟之后第一次执行
long delay, // 保证任务迭代之间必须具有固定长度的延迟
TimeUnit unit) {}
scheduleWithFixedDelay()与scheduleAtFixedRate()方法很类似, 但是不同的是scheduleWithFixedDelay()方法的周期时间间隔是以上一个任务执行结束到下一个
任务开始执行的间隔, 而scheduleAtFixedRate()方法的周期时间间隔是以上一个任务开始执行到下一个任务开始执行的间隔, 也就是这一些任务系列的触发时间都是
可预知的。