【Android】线程池解析

线程池的优点

  1. 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销
  2. 能有效控制线程池的最大并发数,避免大量线程之间因互相抢占系统资源而导致的阻塞现象
  3. 能对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能
ThreadPoolExecutor

ThreadPoolExecutor实现了Java中的Executor接口和ExecutorService接口,是线程池的真正实现。Android中的线程池都是直接或间接通过配置ThreadPoolExecutor实现的。

它的构造方法提供了一系列参数来配置线程池,以下是它的参数最多的构造方法。

java 复制代码
 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

构造方法中各个参数的含义:

  1. corePoolSize(核心线程数):默认情况下,线程池为空,只有提交线程时才会创建线程。如果当前运行的线程数少于corePoolSize,则会创建新的线程来处理任务;如果当前运行的线程数等于或大于corePoolSize则不会创建新线程。
  2. maximumPoolSize(最大线程数):线程中允许的最大线程数量,包括核心线程和非核心线程。当队列满了并且正在执行的线程少于最大线程数时,线程池会尝试创建新新线程来处理任务;如果队列满了并且正在执行的线程已达到最大线程数时,新提交的任务将被拒绝。
  3. keepAliveTime(非核心线程空闲存活时间):这是非核心线程在终止前等待新任务的最大时间。当线程池中的线程数超过核心线程数时,非核心线程会在空闲时会等待新任务的到来,如果超过这个时间还没有新任务,线程将被回收。对于核心线程,除非设置了允许回收的核心线程数(allowCoreThreadTimeOut),否则此参数无效。
  4. unit(时间单位):用于指定keepAliveTime的时间单位,这是一个枚举,常用的有TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分钟)等。
  5. workQueue(工作队列):线程池中的任务队列,用于存放待执行的任务。当核心线程在忙碌时,新提交的任务会放入这个队列,如果队列满了,线程会创建新线程来处理任务,直到最大线程数。
  6. threadFactory(线程工厂):是一个ThreadFactory对象,为线程池提供创建新线程的功能。可以通过实现ThreadFactory接口创建自定义线程的创建过程,如设置线程的名称、优先级、守护状态等。
  7. handler(拒绝/饱和策略):是一个RejectedExecutionHandler对象,用于处理当任务太多,无法被线程池及时处理的情况,即任务队列和线程池都满了的情况。常见的拒绝策略有:
    • AbortPolicy:默认策略,表示无法处理新任务,抛出RejectedExecutionExeption
    • CallerRunsPolicy:在调用者的线程中执行任务
    • DiscardPolicy:丢弃无法处理的任务
    • DiscardOldestPolicy:丢弃队列中最旧的任务,重新提交当前的新任务

核心线程(Core Threads)

核心线程是线程池中一直保持存活的线程,即使它们处于空闲状态。除非设置了allowCoreThreadTimeOut为true,否则核心线程不会因为空闲而被回收。

ThreadPoolExecutor的处理流程与原理

ThreadPoolExecutor的处理大致遵循如下原则:

  • 如果线程池中的线程数量未达到核心线程的数量就会直接启动一个核心线程执行任务
  • 如果线程池中的线程数量已经达到或超过核心线程的数量,则将任务加入任务队列,线程池中的空闲线程会不断从任务线程中取出任务进行处理
  • 如果任务队列已满,并且线程数未达到最大线程数,则创建非核心线程去处理任务
  • 如果任务队列已满且线程数已达到最大线程数,则执行饱和策略
线程池的分类

下面将介绍Android中常见的四类有不同功能特性的线程池,它们直接或间接的通过配置ThreadPoolExecutor来实现自己的功能特性。

FixedThreadPool

通过ExecutorsnewFixedThreadPool方法来创建

java 复制代码
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  • nThreads:核心线程数和最大线程数都为nThreads,即线程池的大小固定
  • 0L:非核心线程数的空闲存活时间为0,因为所有线程都是核心线程,这个值实际上并不会影响线程池的行为
  • TimeUnit.MILLISECONDS:空闲存货时间的时间单位为毫秒
  • new LinkedBlockingQueue<Runnable>():任务队列大小没有限制

由于FixedThreadPool只有核心线程且线程不会回收,所以它能更加快速地响应外界的请求

CachedThreadPool

通过ExecutorsnewCachedThreadPool方法来创建

java 复制代码
 public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  • 0:核心线程数设为0,即线程池中的线程都为非核心线程
  • Integer.MAX_VALUE:最大线程数设为Integer.MAX_VALUE,意味着线程池理论上可以创建非常多的线程
  • 60L:非核心线程的空闲存活时间被设置为60秒,当线程池之中的线程空闲超过这个时间会被回收
  • new SynchronousQueue<Runnable>():任务队列为一个空集合,即任何任务都会被立即执行

CachedThreadPool比较适合执行大量的耗时较少的任务。当整个线程池都处于闲置状态时,线程池中的线程都会超时而停止,此时它几乎不占用任何系统资源。

ScheduledThreadPool

通过ExecutorsnewScheduledThreadPool方法来创建

java 复制代码
 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    
 public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }
  • DEFAULT_KEEPALIVE_MILLIS:是Java线程池中定义的常量,表示非核心线程的空闲存活时间,默认为10ms,这个时间非常短,意味着非核心线程空闲时会很快回收
  • new DelayedWorkQueue():这里使用了一个延迟工作队列,这个队列可以存储待执行的任务,并按照任务的延迟时间进行排序,确保最早需要执行的任务可以优先被处理

这类线程池主要用于执行定时任务和具有固定周期的重复任务。

当执行ScheduledThreadPoolExecutorscheduleAtFixedRatescheduleWithFixedDelay方法,会向DelayedWorkQueue添加一个实现RunnableScheduledFuture接口的任务包装类ScheduledFutureTask,并检查运行的线程是否达到核心线程数corePoolSize。 如果没有就新建线程并启动。但并非立即执行任务,而是去DelayedWorkQueue中取任务包装类ScheduledFutureTask,然后再去执行任务; 如果运行的线程达到了corePoolSize,就把任务添加到任务队列DelayedWorkQueue中;DelayedWorkQueue会将任务排序,先执行的任务放在队列的前面。 当任务执行完后,ScheduledFutureTask中的变量time改为下次要执行的时间,并放回到DelayedWorkQueue中。

SingleThreadExecutor

通过ExecutorsnewScheduledThreadPool方法来创建

java 复制代码
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new AutoShutdownDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }
  • 1:核心线程数和最大线程数都被设置为1,这意味着线程池始终只有一个线程。
  • 0L:非核心线程的空闲存活时间被设置为0。由于只有一个线程,这个值实际上并不会影响线程池的行为。

SingleThreadExecutor的意义在于统一所有的外界任务到一个线程中,这使得在这些任务之间不需要处理线程同步的问题。

总结
线程池类型 特点 使用场景
FixedThreadPool 拥有固定数量的线程,线程数不变 负载较重的服务器,需要限制线程数量的场景
CachedThreadPool 根据需要创建新线程,空闲线程会被回收 执行很多短期异步任务的程序
ScheduledThreadPool 可以安排在给定延迟后运行命令或定期地执行 需要任务在后台定期执行或重复执行的程序
SingleThreadExecutor 只有一个线程,所有任务按照提交顺序依次执行 需要保证任务顺序执行的场景
相关推荐
q***116521 分钟前
总结:Spring Boot 之spring.factories
java·spring boot·spring
追风少年浪子彦36 分钟前
Spring Boot 使用自定义 JsonDeserializer 同时支持多种日期格式
java·spring boot·后端
牢七1 小时前
Javan
java
我叫黑大帅1 小时前
六边形架构?小白也能秒懂的「抗造代码秘诀」
java·后端·架构
不穿格子的程序员1 小时前
Java基础篇——JDK新特性总结
java·虚拟线程·jdk新特性
一乐小哥1 小时前
Java8 党狂喜!这篇把 Java9 到 25 的实用新功能讲透了,新手也能直接抄
java
草莓熊Lotso1 小时前
Git 本地操作进阶:版本回退、撤销修改与文件删除全攻略
java·javascript·c++·人工智能·git·python·网络协议
Ka1Yan1 小时前
[数组] - LeetCode 704. 二分查找
java·开发语言·算法·leetcode·职场和发展
合作小小程序员小小店1 小时前
web网页开发,在线%餐饮点餐%系统,基于Idea,html,css,jQuery,java,ssm,mysql。
java·前端·数据库·html·intellij-idea·springboot