0%

线程池

线程池作用

java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。在并发过程中,合理使用线程池可以带来3个好处

  1. 降低资源消耗: 通过重复利用已创建的线程降低创建和销毁造成的消耗

  2. 提高响应速度: 当任务到达时,任务可以不需要等到线程创建就能立即执行。

  3. 提高线程的可管理性: 线程是稀缺资源,如果无限量的创建,不经不会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。

Executor 框架

Executor 框架是java5之后引进的,Executor框架不仅包过了线程池的管理,还提供了线程工厂、队列以及拒绝策略等,Executor框架让开发编程变的更加简单。

Executor框架

Executor 结构主要包含任务、任务的执行和异步结果的计算

  1. 任务(Runnable/Callable): 包括执行任务需要实现的Runnable接口或Callable接口。

    • Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor 或 ScheduledThreadPoolExecutor执行。
  2. 任务的执行(Executor): 包过任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口。

    • Executor框架 有两个关键类实现了ExecutorService接口(ThreadPoolExecutor 和 SchecduledThreadPoolExecutor)。
  3. 异步结果的计算(Future): 包括接口Future 和实现了Future接口的FutureTasklei。

在 Executor 使用过程中,主线程首先要创建实现 Runnable 或者 Callable 接口的任务对象。工具类 Executors 可以把一个 Runnable 对象封装为一个 Callable 对象(Executors.callable(Runnable task) 或 Executors.callable(Runnable task,Object resule))。

如果执行 ExecutorService.submit(),ExecutorService 将返回一个实现 Future 接口的对象(FutureTask)。
由于 FutureTask 实现了 Runnable,我们也可以创建 FutureTask,然后直接交给 ExecutorService 执行。最后,主线程可以执行 FutureTask.get() 方法来等待任务执行完成。主线程也可以执行 FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。

ThreadPoolExecutor

核心参数

  • corePoolSize: 核心线程数。
    • 定义了最小可以同时运行的线程数据
  • maximumPoolSize: 最大线程数
    • 当队列中存放的任务达到队列容量时,当前可以同时运行的线程数量便为最大线程数。
  • wordQueue: 任务队列
    • 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到了则放入队列中。

常见参数

  • keepAliveTime: 多余线程存活时间
    • 当线程池中线程舒朗大于 corePoolSize时,如果没有新的任务提交,核心线程外的线程不会立即销毁,而是等待,知道等待的时候超过了keepAliveTime才会被销毁
  • unit: 时间单位
    • keepAliveTime参数的时间单位
  • threadFactory: 线程工厂
    • executor创建新线程时用到,可以用来给线程命名,查看当前创建线程数量,设置优先级,设置是否后台运行等。
  • handler: 饱和策略
    ThreadPoolExecutor饱和策略

如果当前同时运行的线程数量达到最大线程数量并且队列液晶被放满时,ThreadPoolTaskExecutor定义了一些策略:

  • ThreadPoolExecutor.AbortPolicy: 抛抛出 RejectedExecutionException 来拒绝新任务的处理。
  • ThreadPoolExecutor.CallerRunsPolicy: 调用执行自己的线程运行任务,也就是直接在调用 execute 方法的线程中运行被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。
  • ThreadPoolExecutor.DiscardPolicy: 不处理新任务,直接丢弃掉。
  • ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃最早的未处理的任务请求。

阿里建议用 ThreadPoolExecutor 构造函数的方式去创建线程池,不建议用 Executors 去创建,弊端如下:

  1. FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。
  2. CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。

线程池执行原理