什么是Executor框架?
Executor是一个多线程管理框架,Executor 框架是 Java5 之后引进的,可以通过该框架来控制线程的启动,执行,关闭,简化Java并发编程。
Executor 框架不仅包括了线程池的管理,还提供了线程工厂、队列以及拒绝策略等,Executor 框架让Java并发编程变得更加简单。
为什么需要Executor框架?
JDK1.5之前我们如果想要使用Java线程来完成相关任务,一般涉及两个类,一个是Thread类,另一个是Runnable接口,通过Runnable和Thread的配合可以编写我们自己的多线程逻辑。
但是如果用这种方式创建线程会出现如下问题:
- 每次执行任务创建线程 new Thread()比较消耗性能,创建一个线程是比较耗时耗资源;
- 调用 new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制的创建,线程之间的相互竞争会导致过多占用系统资源而导致系统瘫痪,还有线程间的频繁交替也会消耗系统资源;
- 接使用new Thread() 启动的线程不利于扩展,比如定时执行、定期执行、定时定期执行、线程中断等都不便实现。
所以通过 Executor 来启动线程,比使用 Thread 的start 方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题。
Executor框架的组成?
Executor框架由三大部分组成:
1)任务
即被执行任务需要实现的接口:Runnable接口或Callable接口;
2)线程池
主要通过ExecutorService接口调用线程池,有2个关键实现类ThreadPoolExecutor和ScheduledThreadPoolExecutor;
3)异步计算的结果
Future接口及其实现类FutureTask。
Executor框架类与接口
主要关注ThreadPoolExecutor和ScheduleThreadPoolExecutor两个类。
ThreadPoolExecutor:线程池的核心实现类,任务的执行器。
ScheduleThreadPoolExecutor:线程池的实现类,任务的执行器,与ThreadPoolExecutor不同的是可以实现延迟或定期执行任务。
Executor框架使用流程
1)主线程首先要创建实现Runnable或者Callable接口的任务对象。
当不需要关注任务执行结果时,使用 Runnable 很合适,反之使用 Callable。
代码举例:
Runnable task = new Runnable() { public void run() { // 任何想要执行的逻辑 } } Callable<String> task = new Callable<String>() { public String call() throws Exception { // 任何想要执行的逻辑 return "任务执行后任何想返回的信息"; } };
2)然后可以把Runnable对象直接交给ExecutorService执行 ,或者也可以把Runnable对象或Callable 对象提交给ExecutorService执行。
ExecutorService 是 Executor 的子接口,其职责是对一堆用于执行任务的线程做管理,即定义了线程池的基本操作接口,有很多具体的实现子类,其核心操作有:
- execute (Runnable):提交 Runnable 任务。
- submit (Callable 或 Runnable):提交 Callable 或 Runnable 任务,并返回代表此任务的 Future 对象。
- shutdown ():关闭新的外部任务提交。
- shutdownNow ():尝试中断正在执行的所有任务,清空并返回待执行的任务列表。
- isTerminated ():测试是否所有任务都执行完毕了。
- isShutdown ():测试是否该 ExecutorService 已被关闭。
3)如果执行 ExecutorService.submit(…),ExecutorService 将返回一个实现Future接口的对象。
Future 接口定义了对任务执行结果的取消、状态查询、结果获取方法。FutureTask 是 Future 的唯一实现类,其职责是提供方便地构建带有返回结果的任务。
Future 接口的核心操作有:
- cancel (boolean):尝试取消已经提交的任务;
- isCancelled ():查询任务是否在完成之前被取消了;
- isDone ():查询任务是否已经完成;
- get ():获取异步任务的执行结果(如果任务没执行完将等待)。
4)最后主线程可以执行 FutureTask.get()方法来等待任务执行完成。
主线程也可以执行 FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。
Executors 的类型
现在我们已经了解了 executors 是什么, 让我们来看看不同类型的 executors。
1.SingleThreadExecutor
此线程池 executor 只有一个线程。它用于以顺序方式的形式执行任务。如果此线程在执行任务时因异常而挂掉,则会创建一个新线程来替换此线程,后续任务将在新线程中执行。
ExecutorService executorService = Executors.newSingleThreadExecutor()
2.FixedThreadPool(n)
顾名思义,它是一个拥有固定数量线程的线程池。提交给 executor 的任务由固定的 n
个线程执行,如果有更多的任务,它们存储在 LinkedBlockingQueue
里。这个数字 n
通常跟底层处理器支持的线程总数有关。
ExecutorService executorService = Executors.newFixedThreadPool(4);
3.CachedThreadPool
该线程池主要用于执行大量短期并行任务的场景。与固定线程池不同,此线程池的线程数不受限制。如果所有的线程都在忙于执行任务并且又有新的任务到来了,这个线程池将创建一个新的线程并将其提交到 executor。只要其中一个线程变为空闲,它就会执行新的任务。如果一个线程有 60 秒的时间都是空闲的,它们将被结束生命周期并从缓存中删除。
但是,如果管理得不合理,或者任务不是很短的,则线程池将包含大量的活动线程。这可能导致资源紊乱并因此导致性能下降。
ExecutorService executorService = Executors.newCachedThreadPool();
4.ScheduledExecutor
当我们有一个需要定期运行的任务或者我们希望延迟某个任务时,就会使用此类型的 executor。
ScheduledExecutorService scheduledExecService = Executors.newScheduledThreadPool(1);
可以使用 scheduleAtFixedRate
或 scheduleWithFixedDelay
在 ScheduledExecutor
中定期的执行任务。
scheduledExecService.scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
scheduledExecService.scheduleWithFixedDelay(Runnable command, long initialDelay, long period, TimeUnit unit)
这两种方法的主要区别在于它们对连续执行定期任务之间的延迟的应答。
scheduleAtFixedRate
:无论前一个任务何时结束,都以固定间隔执行任务。
scheduleWithFixedDelay
:只有在当前任务完成后才会启动延迟倒计时。
Executor框架总结
以上我就主要从Executor框架的组成、工作流程、以及Executor框架的类型作了一个完整的讲解,希望对你掌握Executor框架有所帮助。
陈睿mikechen
10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。
关注「mikechen」公众号,获取更多技术干货!
后台回复【面试】即可获取《史上最全阿里Java面试题总结》,后台回复【架构】,即可获取《阿里架构师进阶专题全部合集》