一文搞懂Executor执行器和线程池的关系,整体介绍其任务执行/调度体系:ThreadPoolExecutor、ScheduledExecutorService(下)

简介: 一文搞懂Executor执行器和线程池的关系,整体介绍其任务执行/调度体系:ThreadPoolExecutor、ScheduledExecutorService(下)

execute()方法执行分析


ThreadPoolExecutor:
    public void execute(Runnable command) {
        if (command == null) throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        } else if (!addWorker(command, false))
            reject(command);
    }


这段代码实际运作非常的复杂,但因为大神对其良好的封装,使得我们理解起来并不难。对提交来的新任务处理步骤用一张图描绘如下:


image.png


对于不需要返回值的任务,使用submit or execute效果一样,但一般情况下推荐统一使用更高层的submit系列方法去提交你的任务。


代码示例

略(相信没有人不会用它吧)。


ScheduledExecutorService

它在ExecutorService接口上再扩展,额外增加了定时、周期执行的能力。


public interface ScheduledExecutorService extends ExecutorService {
  // ========这个两个方法提交的任务只会执行一次========
  // 创建并执行启用的一次性操作在给定的延迟之后。
    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
    // 创建并执行启用的一次性操作在给定的延迟之后。
    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
  // ========这个两个方法提交的任务会周期性的执行多次========
  // 在给定的初始延迟之后,以给定的时间间隔执行周期性动作。
  // 即在 initialDelay 初始延迟后initialDelay + period 执行第一次
  // initialDelay + 2 * period  执行第二次,依次类推
  // 特点:下一次任务的执行并不管你上次任务是否执行完毕
  // 所以它名叫FixedRate:固定的频率执行(每次都是独立事件)
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);
  // 给定的初始延迟之后首先启用的定期动作
  // 随后**上一个执行的终止**和**下一个执行的开始之间**给定的延迟。
  // 也就是说delay表示的是上一次的end和下一次start之间的时间,两次之间是有关系的,不同于上个方法
  // 所以它名叫FixedDelay:两次执行间固定的延迟
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);
}


针对此接口,非常有必要强调如下几点:


  1. 定时执行和周期性执行是两码事
  2. 通过ScheduledExecutorService#schedule()方法调度执行的任务有且仅会执行一次(当然你任务内部怎么去玩就不归ScheduledExecutorService管喽)
  3. 注意:据我了解这是很多小伙伴的误区,以为ScheduledExecutorService执行的任务都会周期性执行的,这是非常错误的理解哦
  4. 还需要提示一点的是:该接口依旧和线程池木有关系,主要看子类如何去实现。而大神给我们提供为“唯一”实现:ScheduledThreadPoolExecutor。



ScheduledThreadPoolExecutor 集大成者


它可谓线程池 + 执行器的集大成者,最强子类:在线程池里执行任务,并且还可以定时、周期性的执行。


public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService { ... }


代码示例

略。


关于它有话说:如果你有定时、周期执行任务的需求,请使用ScheduledThreadPoolExecutor来代替Java古老的Timer/TimerTask API吧。


关于它俩的对比,可参考这篇文章:Java定时任务ScheduledThreadPoolExecutor详解以及与Timer、TimerTask的区别(执行指定次数停止任务)


关于Executors

通过命名可知它是用于创建Executor执行器的工具类(均为静态方法):


  • 可快捷创建带有线程池能力的执行器ThreadPoolExecutor(ExecutorService)
  • 可快速创建具有线程池,且定时、周期执行任务能力的ScheduledThreadPoolExecutor(ScheduledExecutorService)


另外,它还提供几个我认为比较好用的工具方法分享给大家:


Executors:
  // 默认线程工厂:
  // 线程名称为:pool-[poolNum]-thread-[threadNumber]
  // 非守护线程(请注意:非守护线程,看看是否符合你的要求)
    public static ThreadFactory defaultThreadFactory() {
        return new DefaultThreadFactory();
    }
  // 很方便的把一个Runnable适配为一个Callable<T>
  // result可以是个常量值。当然也可以是null
    public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }
    public static Callable<Object> callable(Runnable task) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<Object>(task, null);
    }


关于Executors最后我想说:在生产环境下禁用,禁用,禁用(原因请参照阿里编码规范),一般用于自己测试的时候快速构建方便而为之。


使用线程池,请务必对其七大参数烂熟于胸,否则不要使用,容易酿成大祸或,并且还是软病,很难排查,因此需要敬畏和谨慎。


总结


关于Java中的Executor执行器大体系,以及它和线程池是什么关心就介绍到这,我相信经过本文你应该能彻底了解该体系的框架了吧,不用每次都不知道使用哪个了。


ScheduledExecutorService属于最强接口,它具有全部能力,不过一般若你并不需要定时/周期执行能力的时候,请使用ThreadPoolExecutor/ExecutorService即可。

相关文章
|
2月前
|
算法 Unix Linux
linux线程调度策略
linux线程调度策略
55 0
|
28天前
|
存储 Java 数据处理
进程中的线程调度
进程是应用程序运行的基本单位,包括主线程、用户线程和守护线程。计算机由存储器和处理器协同操作,操作系统设计为分时和分任务模式。在个人PC普及后,基于用户的时间片异步任务操作系统确保了更好的体验和性能。线程作为进程的调度单元,通过覆写`Thread`类的`run`方法来处理任务数据,并由系统调度框架统一管理。微服务架构进一步将应用分解为多个子服务,在不同节点上执行,提高数据处理效率与容错性,特别是在大规模数据存储和处理中表现显著。例如,利用微服务框架可以优化算法,加速业务逻辑处理,并在不同区块间分配海量数据存储任务。
|
2月前
|
存储 监控 Java
|
2月前
|
监控 Java
ThreadPoolExecutor 线程执行超时,释放线程
ThreadPoolExecutor 线程执行超时,释放线程
54 1
|
2月前
|
前端开发 JavaScript 大数据
React与Web Workers:开启前端多线程时代的钥匙——深入探索计算密集型任务的优化策略与最佳实践
【8月更文挑战第31天】随着Web应用复杂性的提升,单线程JavaScript已难以胜任高计算量任务。Web Workers通过多线程编程解决了这一问题,使耗时任务独立运行而不阻塞主线程。结合React的组件化与虚拟DOM优势,可将大数据处理等任务交由Web Workers完成,确保UI流畅。最佳实践包括定义清晰接口、加强错误处理及合理评估任务特性。这一结合不仅提升了用户体验,更为前端开发带来多线程时代的全新可能。
29 0
|
2月前
|
Cloud Native Java 调度
项目环境测试问题之线程同步器会造成执行完任务的worker等待的情况如何解决
项目环境测试问题之线程同步器会造成执行完任务的worker等待的情况如何解决
|
2月前
|
Java 测试技术 PHP
父子任务使用不当线程池死锁怎么解决?
在Java多线程编程中,线程池有助于提升性能与资源利用效率,但若父子任务共用同一池,则可能诱发死锁。本文通过一个具体案例剖析此问题:在一个固定大小为2的线程池中,父任务直接调用`outerTask`,而`outerTask`再次使用同一线程池异步调用`innerTask`。理论上,任务应迅速完成,但实际上却超时未完成。经由`jstack`输出的线程调用栈分析发现,线程陷入等待状态,形成“死锁”。原因是子任务需待父任务完成,而父任务则需等待子任务执行完毕以释放线程,从而相互阻塞。此问题在测试环境中不易显现,常在生产环境下高并发时爆发,重启或扩容仅能暂时缓解。
|
3月前
|
Java Linux
Java演进问题之1:1线程模型对于I/O密集型任务如何解决
Java演进问题之1:1线程模型对于I/O密集型任务如何解决
|
3月前
|
安全
线程操纵术并行策略问题之ForkJoinTask提交任务的问题如何解决
线程操纵术并行策略问题之ForkJoinTask提交任务的问题如何解决
|
3月前
线程操纵术并行策略问题之ForkJoinTask提交任务的问题如何解决
线程操纵术并行策略问题之ForkJoinTask提交任务的问题如何解决