我们知道,线程需要的时候要进行创建,不需要的时候需要进行销毁,但是线程的创建和销毁都是一个开销比较大的操作。
为什么开销大呢?
虽然我们程序员创建一个线程很容易,直接使用 new Thread() 创建就可以了,但是操作系统做的工作会多很多,它需要发出 系统调用
,陷入内核,调用内核 API 创建线程,为线程分配资源等,这一些操作有很大的开销。
所以,在高并发大流量的情况下,频繁的创建和销毁线程会大大拖慢响应速度,那么有什么能够提高响应速度的方式吗?方式有很多,尽量避免线程的创建和销毁是一种提升性能的方式,也就是把线程 复用
起来,因为性能是我们日常最关注的因素。
本篇文章我们先来通过认识一下 Executor 框架、然后通过描述线程池的基本概念入手、逐步认识线程池的核心类,然后慢慢进入线程池的原理中,带你一步一步理解线程池。
在 Java 中可以通过线程池来达到这样的效果。今天我们就来详细讲解一下 Java 的线程池
。
Executor 框架
为什么要先说一下 Executor 呢?因为我认为 Executor 是线程池的一个驱动,我们平常创建并执行线程用的一般都是 new Thread().start() 这个方法,这个方法更多强调 创建一个线程并开始运行。而我们后面讲到创建线程池更多体现在驱动执行上。
Executor 的总体框架如下,我们下面会对 Executor 框架中的每个类进行介绍。
我们首先来认识一下 Executor
Executor 接口
Executor 是 java.util.concurrent
的顶级接口,这个接口只有一个方法,那就是 execute
方法。我们平常创建并启动线程会使用 new Thread().start()
,而 Executor 中的 execute 方法替代了显示创建线程的方式。Executor 的设计初衷就是将任务提交和任务执行细节进行解藕。使用 Executor 框架,你可以使用如下的方式创建线程
Executor executor = Executors.xxx // xxx 其实就是 Executor 的实现类,我们后面会说 executor.execute(new RunnableTask1()); executor.execute(new RunnableTask2());
execute方法接收一个 Runnable
实例,它用来执行一个任务,而任务就是一个实现了 Runnable 接口的类,但是 execute 方法不能接收实现了 Callable
接口的类,也就是说,execute 方法不能接收具有返回值的任务。
execute 方法创建的线程是异步执行的,也就是说,你不用等待每个任务执行完毕后再执行下一个任务。
比如下面就是一个简单的使用 Executor 创建并执行线程的示例
public class RunnableTask implements Runnable{ @Override public void run() { System.out.println("running"); } public static void main(String[] args) { Executor executor = Executors.newSingleThreadExecutor(); // 你可能不太理解这是什么意思,我们后面会说。 executor.execute(new RunnableTask()); } }
Executor 就相当于是族长,大佬只发号令,族长让你异步执行你就得异步执行,族长说不用汇报
任务你就不用回报,但是这个族长管的事情有点少,所以除了 Executor 之外,我们还需要认识其他管家,比如说管你这个线程啥时候终止,啥时候暂停,判断你这个线程当前的状态等,ExecutorService
就是一位大管家。
ExecutorService 接口
ExecutorService 也是一个接口,它是 Executor 的拓展,提供了一些 Executor 中没有的方法,下面我们来介绍一下这些方法
void shutdown();
shutdown
方法调用后,ExecutorService 会有序关闭正在执行的任务,但是不接受新任务。如果任务已经关闭,那么这个方法不会产生任何影响。
ExecutorService 还有一个和 shutdown 方法类似的方法是
List<Runnable> shutdownNow();
shutdownNow
会尝试停止关闭所有正在执行的任务,停止正在等待的任务,并返回正在等待执行的任务列表。
既然 shutdown 和 shutdownNow 这么相似,那么二者有啥区别呢?
- shutdown 方法只是会将
线程池
的状态设置为SHUTWDOWN
,正在执行的任务会继续执行下去,线程池会等待任务的执行完毕,而没有执行的线程则会中断。- shutdownNow 方法会将线程池的状态设置为
STOP
,正在执行和等待的任务则被停止,返回等待执行的任务列表
ExecutorService 还有三个判断线程状态的方法,分别是
boolean isShutdown(); boolean isTerminated(); boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
isShutdown
方法表示执行器是否已经关闭,如果已经关闭,返回 true,否则返回 false。isTerminated
方法表示判断所有任务再关闭后是否已完成,如果完成返回 false,这个需要注意一点,除非首先调用 shutdown 或者 shutdownNow 方法,否则 isTerminated 方法永远不会为 true。awaitTermination
方法会阻塞,直到发出调用 shutdown 请求后所有的任务已经完成执行后才会解除。这个方法不是非常容易理解,下面通过一个小例子来看一下。
public static ExecutorService executorService = Executors.newFixedThreadPool(10); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) { executorService.submit(() -> { System.out.println(Thread.currentThread().getName()); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } }); } executorService.shutdown(); System.out.println("Waiting..."); boolean isTermination = executorService.awaitTermination(3, TimeUnit.SECONDS); System.out.println("Waiting...Done"); if(isTermination){ System.out.println("All Thread Done"); } System.out.println(Thread.currentThread().getName()); }
如果在调用 executorService.shutdown() 之后,所有线程完成任务,isTermination 返回 true,程序才会打印出 All Thread Done ,如果注释掉 executorService.shutdown() 或者在任务没有完成后 awaitTermination 就超时了,那么 isTermination 就会返回 false。
ExecutorService 当大管家还有一个原因是因为它不仅能够包容 Runnable 对象,还能够接纳 Callable
对象。在 ExecutorService 中,submit
方法扮演了这个角色。
<T> Future<T> submit(Callable<T> task); <T> Future<T> submit(Runnable task, T result); Future<?> submit(Runnable task);
submit 方法会返回一个 Future
对象,<T>
表示范型,它是对 Callable 产生的返回值来说的,submit 方法提交的任务中的 call 方法如果返回 Integer,那么 submit 方法就返回 Future<Integer>
,依此类推。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException; <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;
invokeAll
方法用于执行给定的任务结合,执行完成后会返回一个任务列表,任务列表每一项是一个任务,每个任务会包括任务状态和执行结果,同样 invokeAll 方法也会返回 Future 对象。
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException; <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
invokeAny 会获得最先完成任务的结果,即Callable<T>
接口中的 call 的返回值,在获得结果时,会中断其他正在执行的任务,具有阻塞性
。
大管家的职责相对于组长来说标准更多,管的事情也比较宽,但是大管家毕竟也是家族的中流砥柱,他不会做具体的活,他的下面有各个干将,干将是一个家族的核心,他负责完成大管家的工作。