多线程与高并发(14)——Executor框架(线程池基础)
创始人
2024-03-27 03:04:13
0

一、简介

线程是什么,线程是一个任务的工作单元和执行单元。我们在用线程的时候知道,要创建线程,再执行线程。如果任务多的情况呢,会有大量的创建销毁线程的资源消耗,这时候就引入了线程池的概念。
JDK5开始,把工作单元和执行单元分开,工作单元包括Runnable和Callable,利用Executor来执行任务。
通过 Executor来启动线程比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题。
this 逃逸是指在构造函数返回之前其他线程就持有该对象的引用,调用尚未构造完全的对象的方法可能引发令人疑惑的错误。

二、Executor框架结构

1、任务: 包括被执行任务需要实现的接口,Runnable接口或者Callable接口。
2、任务的提交和执行: 包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口。Executor框架有两个关键类实现了ExecutorService接口(ThreadPoolExecutor和ScheduleThreadPoolExecutor)。ExecutorService接口中补充了任务的提交方法submit()。
3、任务的结果(异步计算): 包括接口Future和实现Future接口的FutureTask类。
Executor翻译过来就是执行器的意思,也就是说,Executor框架的目的就是调度任务进行执行,并得出异步计算的结果。

1、任务(Runnable和Callable接口)

主线程通过实现 Runnable 或者 Callable 接口,创建任务对象,用于后续执行。
Runnable或Callable接口的实现类都可以被 ThreadPoolExecutor 或ScheduledThreadPoolExecutor 执行。
区别:Runnable接口不会抛异常或者返回对象,但是 Callable 接口可以。
代码如下:

@FunctionalInterface
public interface Callable {/*** Computes a result, or throws an exception if unable to do so.** @return computed result* @throws Exception if unable to compute a result*/V call() throws Exception;
}
@FunctionalInterface
public interface Runnable {/*** When an object implementing interface Runnable is used* to create a thread, starting the thread causes the object's* run method to be called in that separately executing* thread.* 

* The general contract of the method run is that it may* take any action whatsoever.** @see java.lang.Thread#run()*/public abstract void run(); }

2、任务的提交和执行

Executor常用的类图如下:
在这里插入图片描述
任务执行机制的核心接口 Executor源码如下:

public interface Executor {/*** Executes the given command at some time in the future.  The command* may execute in a new thread, in a pooled thread, or in the calling* thread, at the discretion of the {@code Executor} implementation.** @param command the runnable task* @throws RejectedExecutionException if this task cannot be* accepted for execution* @throws NullPointerException if command is null*/void execute(Runnable command);
}

通过代码可以看出,其中定义了任务的执行。
execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
ExecutorService接口中提供了submit()方法,用于提交需要返回值的任务。
代码如下:

 Future submit(Callable task);
 Future submit(Runnable task, T result);
Future submit(Runnable task);

线程池会返回一个 Future 类型的对象。

3、任务的结果

Future存储的是未来产生的一个结果,其中的get()方法是阻塞的,用来获取返回值。
代码如下:

	V get() throws InterruptedException, ExecutionException;V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;

使用 get(long timeout,TimeUnit unit)方法的话,如果在 timeout 时间内任务还没有执行完,就会抛出 java.util.concurrent.TimeoutException。
FutureTask接口表示Future+Runnable,其类图如下:
在这里插入图片描述
FutureTask表示可以直接创建一个FutureTask对象,交给线程池去执行,并且有返回结果。

三、Executor框架使用

下面这个图出现的次数很多了:
在这里插入图片描述
1、主线程创建任务对象,这个任务对象实现了Runnable或 Callable接口。
2、把任务对象交给执行器ExecutorService去执行:

ExecutorService.execute(Runnable command);
 Future submit(Callable task);
 Future submit(Runnable task, T result);
Future submit(Runnable task);

3、主线程可以直接创建一个FutureTask对象交给执行器去执行。
4、主线程可以执行 FutureTask.get()方法来等待任务执行完成。主线程也可以执行 FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。

四、 示例代码

1、Future

submit()和get()方法:

 public static void main(String[] args) throws ExecutionException, InterruptedException {//线程池ExecutorService executorService = Executors.newFixedThreadPool(3);//提交并执行任务Future submit = executorService.submit(() -> {try {Thread.sleep(5000L);System.out.println("5秒过去啦");} catch (InterruptedException e) {e.printStackTrace();}return "我是返回值";});String s = submit.get();System.out.println(s);//关闭线程池executorService.shutdown();}

结果如下:

5秒过去啦
我是返回值

get(long timeout,TimeUnit unit)方法:

public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {//线程池ExecutorService executorService = Executors.newFixedThreadPool(3);//提交并执行任务Future submit = executorService.submit(() -> {try {Thread.sleep(5000L);} catch (InterruptedException e) {e.printStackTrace();}return "我是返回值";});//3秒就不行了哦String s = submit.get(3, TimeUnit.SECONDS);System.out.println(s);//关闭线程池executorService.shutdown();}

运行结果如下:
在这里插入图片描述
抛出异常,且线程阻塞未结束。

2、Callable

 public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {//创建任务Callable callable = new Callable() {@Overridepublic String call() throws Exception {return "我是callable";}};//线程池ExecutorService executorService = Executors.newFixedThreadPool(3);//提交并执行任务Future submit = executorService.submit(callable);//3秒就不行了哦String s = submit.get(3, TimeUnit.SECONDS);System.out.println(s);//关闭线程池executorService.shutdown();}

执行结果如下:

我是callable

3、FutureTask

public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {//线程池ExecutorService executorService = Executors.newFixedThreadPool(3);//创建并执行任务FutureTask task = new FutureTask<>(()->{return "aaaaaaa";});executorService.submit(task);String s = task.get();System.out.println(s);//关闭线程池executorService.shutdown();}

执行结果如下:

aaaaaaa

五、总结&&常见对比

1、Runnable vs Callable

前者无返回值和抛异常,后者有。

2、execute vs submit

前者执行不需要返回值的任务,后者执行需要返回值的任务,返回值为Future。

3、shutdown vs shutdownNow

shutdown() :关闭线程池,线程池的状态变为 SHUTDOWN。线程池不再接受新任务了,但是队列里的任务得执行完毕。(新任务停止,已存在的要执行完
shutdownNow():关闭线程池,线程的状态变为 STOP。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的 List。(停止所有任务并返回等待值

4、isTerminated vs isShutdown

isTerminated () :调用 shutdown() 方法后,并且所有提交的任务完成后返回为 true。
isShutdown():调用 shutdown() 方法后返回为 true。

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...