Java多线程之Executor、ExecutorService、Executors、Callable、Future与FutureTask

使用Executors类来创建线程池。


通过执行器你无需自己创建与控制Thread对象。比如,你不用在代码中编写new Thread或者thread1.start()也一样可以使用多线程。如下例:

ExecutorService exec = Executors.newCachedThreadPool();

for (int i = 0; i < 5; i++) { //5个任务

exec.submit(new Runnable() {

@Override

public void run() {

System.out.println(Thread.currentThread().getName()+" doing task");

}

});

}

exec.shutdown(); //关闭线程池

输出如下:

pool-1-thread-2 doing task

pool-1-thread-1 doing task

pool-1-thread-3 doing task

pool-1-thread-4 doing task

pool-1-thread-5 doing task

从输出我们可以看到,exec使用了线程池1中的5个线程做了这几个任务。

这个例子中exec这个Executor负责线程管理任务,所谓的任务在这里就是实现了Runnable接口的匿名类。至于要使用几个线程,什么时候启动这些线程,是用线程池还是用单个线程来完成这些任务,我们无需操心。完全由exec这个执行器来负责。在这里exec(newCachedThreadPool)指向是一个可以根据需求创建新线程的线程池。

Executors相当于执行器的工厂类,包含各种常用执行器的静态工厂方法,可以直接创建常用的执行器。几种常用的执行器如下:

Executors.newCachedThreadPool,根据需要可以创建新线程的线程池。线程池中曾经创建的线程,在完成某个任务后也许会被用来完成另外一项任务。

Executors.newFixedThreadPool(int nThreads) ,创建一个可重用固定线程数的线程池。这个线程池里最多包含nThread个线程。起到限制并发线程数的作用。

Executors.newSingleThreadExecutor() ,创建一个使用单个 worker 线程的 Executor。即使任务再多,也只用1个线程完成任务。会顺序执行提交的任务。

Executors.newSingleThreadScheduledExecutor() ,创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期执行

newSingleThreadExecutor例子如下:

ExecutorService exec = Executors.newSingleThreadExecutor();

for (int i = 0; i < 5; i++) {

exec.execute(new Runnable() {//execute方法接收Runnable对象,无返回值

@Override

public void run() {

System.out.println(Thread.currentThread().getName());

}

});

}

exec.shutdown();

输出如下:

pool-1-thread-1

pool-1-thread-1

pool-1-thread-1

pool-1-thread-1

pool-1-thread-1

可以看出,虽然有5个任务(5个new Runnable),但是只由1个线程来完成。

**最佳实践:**我们应直接使用现有Executors的静态工厂方法生成所需的线程池,然后利用线程池来执行任务,而不要自己创建线程。

Executor与ExecutorService的常用方法


execute方法

Executor接口只有void execute(Runnable command)方法。从方法声明中可以看到入参为Runnable类型。常用例子如下:

Executor executor = anExecutor;

executor.execute(new RunnableTask1());

但里面具体怎么执行,是否调用线程则由相应的Executor接口实现类决定。比如,前面的newCachedThreadPool使用线程池来进行执行。

可以看出,Executor将任务提交与每个任务如何运行(如何使用线程、调度)相分离。

submit方法

ExecutorService接口继承自Executor接口,扩展了父接口中的execute方法。有两个常用的submit方法

Future<?> submit(Runnable task)

Future submit(Callable task)

可以看到,这两个常用方法一个接收Runnable类型入参,一个接收Callable类型入参。

Callable入参允许任务返回值,而Runnable无返回值。

也就是说如果我们希望线程有一个返回结果,我们应该使用Callable类型入参。

invokeAll与invokeAny方法

批量执行一组Callable任务。

invokeAll是等所有任务完成后,返回代表结果的Future列表。

invokeAny是等一批任务中的任何一个任务完成后就返回。

从两个方法的返回结果我们也可以看出两个方法的不同:

List<Future> invokeAll(Collection<? extends Callable> tasks)

T invokeAny(Collection<? extends Callable> tasks)

invokeAll返回的是List,而invoke返回的是T

shutdown()方法

启动一次顺序关闭,执行以前提交的任务,但不接受新任务。

执行此方法后,线程池等待任务结束后就关闭,同时不再接收新的任务。

如果执行完shutdown()方法后,再去执行execute方法则直接抛出RejectedExecutionException

不要问我为什么知道…刚从坑里爬出来。

原则:只要ExecutorService(线程池)不再使用,就应该关闭,以回收资源。要注意这个不再使用

上述方法较多,可以配合后面的实例进行理解。可以先记住execute方法与shutdown方法。

3. Callable与Future

===================

Callable接口


Runnable接口中的public void run()方法无返回值,如果我们希望线程运算后将结果返回,使用Runnable就无能为力。

这时我们应使用Callable接口。**Callable代表有返回值的任务。**一个实现Callable接口的类如下所示:

class CalcTask implements Callable {

@Override

public String call() throws Exception {

return Thread.currentThread().getName();

}

}

这个任务比较简单,就是返回当前线程的名字。与Runnable相比较有一个返回值,此例中返回值类型为String。

使用如下代码进行调用:

ExecutorService exec = Executors.newCachedThreadPool();

List<Callable> taskList = new ArrayList<>();

/* 往任务列表中添加5个任务 */

for (int i = 0; i < 5; i++) {

taskList.add(new CalcTask()); // CalcTask为实现Callable接口的类,有返回值。

}

/* 结果列表:存放任务完成返回的值 */

List<Future> resultList = new ArrayList<>(); // Future用来存放任务返回值,T为返回值得类型

try {

/invokeAll批量运行所有任务。submit提交单个任务/

resultList = exec.invokeAll(taskList);

} catch (InterruptedException e) {

e.printStackTrace();

}

try {

/从future中输出每个任务的返回值/

for (Future future : resultList) {

System.out.println(future.get()); // get方法会阻塞直到结果返回。返回值类型为String

}

} catch (InterruptedException e) {

e.printStackTrace();

} catch (ExecutionException e) {

e.printStackTrace();

}

输出如下:

pool-1-thread-1

pool-1-thread-2

pool-1-thread-3

pool-1-thread-4

pool-1-thread-5

Future接口


上面的例子中使用了Future接口。Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。

上面的例子中exec执行器执行了一个Callable类型的任务列表然后得到了Futuer类型的结果列表resultList。

Future.get方法

等待计算完成,然后获取其结果。

Future.isDone方法

用来查询任务是否做完。

例子如下:

/新建一个Callable任务/

Callable callableTask = new Callable<>() {

@Override

public Integer call() throws Exception {

System.out.println(“Calculating 1+1!”);

TimeUnit.SECONDS.sleep(2);//休眠2秒

return 2;

}

};

ExecutorService executor = Executors.newCachedThreadPool();

Future result = executor.submit(callableTask);

executor.shutdown();

while(!result.isDone()){ // isDone()方法可以查询子线程是否做完

System.out.println(“子线程正在执行”);

TimeUnit.SECONDS.sleep(1); // 休眠1秒

}

try {

System.out.println(“子线程执行结果:”+result.get());

} catch (InterruptedException | ExecutionException e) {

e.printStackTrace();

}

输出如下:

Calculating 1+1!

子线程正在执行

子线程正在执行

子线程执行结果:2

4.FutureTask

============

FutureTask类是 Future 接口的一个实现。FutureTask类实现了RunnableFuture接口,RunnableFuture继承了Runnable接口和Future接口,所以:

  1. FutureTask可以作为Runnable被线程执行
    自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

关于面试刷题也是有方法可言的,建议最好是按照专题来进行,然后由基础到高级,由浅入深来,效果会更好。当然,这些内容我也全部整理在一份pdf文档内,分成了以下几大专题:

  • Java基础部分

  • 算法与编程

  • 数据库部分

  • 流行的框架与新技术(Spring+SpringCloud+SpringCloudAlibaba)

这份面试文档当然不止这些内容,实际上像JVM、设计模式、ZK、MQ、数据结构等其他部分的面试内容均有涉及,因为文章篇幅,就不全部在这里阐述了。

作为一名程序员,阶段性的学习是必不可少的,而且需要保持一定的持续性,这次在这个阶段内,我对一些重点的知识点进行了系统的复习,一方面巩固了自己的基础,另一方面也提升了自己的知识广度和深度。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
04208)]

  • 算法与编程

[外链图片转存中…(img-405iYGYp-1713136504208)]

  • 数据库部分

[外链图片转存中…(img-gWc4FL5Y-1713136504208)]

  • 流行的框架与新技术(Spring+SpringCloud+SpringCloudAlibaba)

[外链图片转存中…(img-F1lPOQ9Q-1713136504208)]

这份面试文档当然不止这些内容,实际上像JVM、设计模式、ZK、MQ、数据结构等其他部分的面试内容均有涉及,因为文章篇幅,就不全部在这里阐述了。

作为一名程序员,阶段性的学习是必不可少的,而且需要保持一定的持续性,这次在这个阶段内,我对一些重点的知识点进行了系统的复习,一方面巩固了自己的基础,另一方面也提升了自己的知识广度和深度。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 30
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值