APP下载

执行绪池?不懂也会被问到的任务执行器

消息来源:baojiabao.com 作者: 发布时间:2026-05-28

报价宝综合消息执行绪池?不懂也会被问到的任务执行器

作者 | Java圣斗士 | 原创图文,转载请注明出处

全文2000字,阅读可能需要点时间,建议收藏

哈喽大家好,我是又皮又可爱的Java圣斗士,关注我,每天带你飞!

小伙伴们都知道,在初级Java工程师的面试题中,总会被问到诸如如何启动一个执行绪?如何执行一个任务?start()和run()哪一个才是启动执行绪,等等诸如此类的低阶问题,我们都知道执行绪可以通过两种方式来实现,继承Thread类或者实现Runnable界面。我们也可以通过像下面的程式码这样快速启动一个执行绪:

new Thread(() -> {

// some codes run here

}).start();

但是很显然,这种程式码太Demo了,作为某些知识的演示还好,但是实际的并发场景如此复杂,这样的程式码可无法胜任!

那么如何更加专业地设计执行绪的执行策略呢?今天我们就来讨论这个话题!

我们都知道,我们的并发程式都是通过“执行任务”的机制来设计的,而任务通常就是一些抽象且离散的工作单元。当围绕“任务执行”来设计应用程序结构时,第一步就是要找出清晰的任务边界。

在理想情况下,各个任务之间是相互独立的:任务并不依赖于其他任务的状态、结果或边界效应。

执行绪数量的限制

在《Java并发程式设计实战》中,当描述执行绪数量限制的时候,引出了一个重要的问题,当执行绪过多的时候,系统内存会不会因此而丢掷OutOfMemoryError?为了不破坏系统的稳定性,在我们可建立的执行绪数量上存在一个限制

这个限制受平台以及多个因素影响,包括JVM启动引数、Thread建构函式中请求的栈大小、底层操作系统对执行绪的限制等。

正因为有了这种限制,上面的程式码才无法胜任实际业务需求,因为通过new关键字,轻易地将执行绪创建出来是对系统内存的一种不负责任的消耗。而正确的执行绪执行策略应该是通过Executor来完成。

public interface Executor {

void execute(Runnable command);

}

Executor是一个非常简单的界面,它的语义是“执行器”,我们通过向execute()方法传入一个Runnable来执行任务。

Executor本身是基于生产者消费者,它将任务的定义执行解耦开来,提交任务相当于生产者,而执行任务相当于消费者,所以,如果程式中需要实现一个生产者-消费者的设计,那么最简单的方式通常就是使用Executor。而我们前面也说过,所有的执行绪几乎都围绕着“执行任务”而展开,从某种层面上来讲,Executor就是我们定义任务、执行任务的首选方案!

四大执行绪池

常用的执行绪池有四种,这也是面试中经常问到的:你用过哪些执行绪池?没听过可就糟糕了。

fixedThreadPool

cachedThreadPool

scheduledThreadPool

singleThreadExecutor

它们的建立方式如下:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

我们通过Executors这个工具类的工厂方法来建立对应的执行绪池,这些工厂方法分别呼叫了ThreadPoolExecutor和ScheduledExecutorService的一系列构造器,它们之间的继承关系图谱我已经整理出来了,如下所示:

来说说这四个工厂方法:

1、newFixedThreadPool(int) :建立一个定额执行绪池,每提交一个任务建立一个执行绪,达到数量限制后不再增加,这时执行绪池的规模将不再变化(如果某个执行绪由于发生了未预期的异常而结束,那么执行绪池会补充一个新的执行绪)

2、newCachedThreadPool() : 建立一个可快取的执行绪池,执行绪池的规模不存在任何限制,当执行绪多余任务时,回收空闲执行绪;当任务增加时,建立新执行绪。

3、newSingleThreadExecutor:单执行绪的Executor,如果这个执行绪异常结束,会建立另一个执行绪来替代。NewSingleThreadExecutor能确保依照任务在伫列中的顺序序列执行(例如FIFO、LIFO、优先级)。

4、newScheduleThreadPool:建立一个固定长度的执行绪池,而且以延迟或定时的方式来执行任务,类似于Timer。

Executor的生命周期

Executor界面简单的定义了任务提交后的执行方法execute(),而执行器的生命周期必须通过某些额外的方法才能够实现,比如shutdown()等,这些方法均扩充套件在了它的子界面ExecutorService中:

public interface ExecutorService extends Executor {

void shutdown();

List shutdownNow();

boolean isShutdown();

boolean isTerminated();

boolean awaitTermination(long timeout, TimeUnit unit)

throws InterruptedException;

// ......其他用于任务提交的便利方法

}

这五个方法是宣告周期管理的方法。那么可以得出Executor的三种状态:

执行、关闭、终止

ExecutorService在初始建立时处于执行状态shutdown()方法将执行平缓的关闭过程:不再接受新的任务,同时等待已经提交的任务执行完成——包括那些还未开始执行的任务。

shutdownNow()方法将执行粗暴的关闭方式:它将尝试取消所有执行中的任务,并且不再启动伫列中尚未开始执行的任务。

延迟任务与周期任务

有时候,我们实现一个“闹钟”功能会用到Timer,这个类可以管理任务以及周期任务,但是它本身存在一定缺陷,其缺陷在于Timer在执行所有定时任务时只会建立一个执行绪,如果某个执行绪的执行时间过长,那么势必破坏任务定时的准确性。

因此,通常用scheduleThreadPoolExecutor来代替使用。

往期精彩:

《技术新人不知道如何提升自己?教你一招,坚持下去准没错!》

《用惯了框架的分页API?今天教你手写分页查询》

《不会MySQL效能优化?21条最佳经验让HR对你刮目相看》

---欢迎关注【Java圣斗士】,我是你们的小可爱(✪ω✪) Morty---

---专注IT职场经验、IT技术分享的灵魂写手---

---每天带你领略IT的魅力---

---期待与您陪伴!---

2019-12-02 12:21:00

相关文章