Android 中的线程池

线程池的概念

线程池(thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 例如,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销。

任务调度以执行线程的常见方法是使用同步队列,称作任务队列。池中的线程等待队列中的任务,并把执行完的任务放入完成队列中。

Android 中的线程池有哪些?

Android 中的线程池的概念来源于 Java 中的 Executor,executor 是一个接口,真正的线程池的实现为 ThreadPoolExecutor。ThreadPoolExecutor 提供了一系列参数来配置线程池,通过不同的参数可以创建不同的线程池。

为了使在很广泛的情境下可以利用,ThreadPoolExecutor 提供了很多可调整参数和可扩展的回调。然而,强别建议编程人员使用更方便的 Executor 工厂方法:

  • newCacheTreadPool()(无限制的线程池,自动线程回收)
  • newFixedThreadPool(int)(固定大小线程池)
  • newSingleThreadExecutor()(单个后台线程)
  • newScheduledThreadPool()(核心线程数固定,非核心线程数无限大的线程池)

接下来,当手动配置和调用设个 ThreadPoolExecutor 会使用的下面的信息

核心和最大线程池大小

ThreadPoolExecutor 根据核心线程大小(见 getCorePoolSize()))和最大线程大小(见 getMaximumPoolSize()) 的设置边界来调整线程池的大小。当一个新的任务在 execute(Runnable) 方法中提交,并且此时正在运行的线程数量小于核心线程数量,那么会创建一个新的线程处理请求,即使其他线程处于闲置状态。如果有大于核心线程数量,但小于最大线程数量的线程运行,此时如果队列已满,会闯将一个新的线程。通过设置核心线程数量等于最大线程数量,你就创建了一个固定大小的线程池。通过设置最大线程数量问理论上无限大的值,例如:Integer.MAX_VALUE,你可以让这个线程池容纳任意数量的并发任务。通常情况下,核心和最大线程池数量只在构造器中设置,但是它们也可以使用setCorePoolSize(int)setMaximumPoolSize(int)动态改变。

根据需求构建

默认情况下,核心线程只在收到新任务的时候初始化创建和开启线程,但是这一过程可以重写prestartCoreThread()prestartAllCoreThreads()动态的使用线程池。如果你构建一个不是空队列的线程池,那么你可能需要提前开启线程。

创建新线程

新线程使用 ThreadFactory 创建。如果没有指定,会使用 defaultThreadFactory()),创建相同的 ThreadGroup 拥有相同优先级和非守护线程状态的线程。通过提供不同的 ThreadFactory,你可以改变线程的名字,线程组优先级,守护状态等。如果 ThreadFactory 调用 newThread 方法返回 null 时,ThreadFactory 创建线程会失败,此时 executor 会继续运行,但不能执行任何任务。线程应该拥有 “modifyThread” 的运行时权限。如果工作线程或其他线程池中的线程没有这个权限,系统将给与警告:配置更改可能无法及时生效,关闭线程池可能保持在终止可能尚未完成的状态。

线程池有什么区别

FixedThreadPool

它是一种线程数量固定的线程池,线程处于闲置状态时,它们并不会被回收,除非线程池被关闭了。当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来。由于 FixedThreadPool 只有核心线程并且这些核心线程不会被回收,这意味着它能够更加快速的响应外界的请求。newFixedThreadPool 方法的实现如下,可以发现 FixedThreadPool 中只有核心线程并且这些核心线程没有超时机制,另外任务队列也是没有大小限制的。

1
2
3
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}

CachedThreadPool

通过 Executors 的 newCachedThreadPool 方法创建。它是一种线程数量不定的线程池,它只有非核心线程,并且其最大线程数为 Integer.MAX_VALUE。由于 Integer.MAX_VALUE 是一个很大的数,实际上就相当于最大线程数可以无限大。当线程中的线程都处于活动状态时,线程池会创建新的线程来处理新任务,否则就会利用空闲的线程来处理新任务。线程池中的空闲线程都有超时机制,这个超时时长为 60 秒,超过 60 秒限制线程就会被回收。和 FixedThreadPool 不同的是,CachedThreadPool 的任务队列其实相当于一个空集合,这将导致任何任务都会立即被执行,因为在这种场景下 SynchronousQueue 是无法插入任务的。SynchronousQueue 是一个非常特殊的队列,在很多情况下可以把它简单理解为一个无法存储元素的队列。从 CachedThreadPool 的特性来看,这类线程比较适合执行大量的耗时较少的任务。当整个线程池都处于闲置状态时,线程池中的线程都会超时而被停止,这个时候 CachedThreadPool 之中实际上是没有任何线程的,它几乎是不占用任何系统资源的。newCachedThreadPool 方法实现如下所示。

1
2
3
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}

ScheduledThreadPool

通过 Executors 的 newScheduledThreadPool 方法创建。它的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程限制时会被立即回收。ScheduledThreadPool 这类线程池主要用于执行定时任务和具有固定周期的重复任务, newScheduledThreadPool 方法的实现如下所示。

1
2
3
4
5
6
7
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}

SingleThreadExecutor

通过 Executors 的 newSingleThreadExecutor 方法创建。这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行。SingleThreadExecutor 的意义在于同一所有的外接任务到一个线程中,这使得这些任务之间不需要处理线程同步的问题。SingleThreadExecutor 方法的实现如下所示。

1
2
3
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}

为什么要使用线程池

线程池有如下有点:

  • 重用线程池中的线程, 避免因为线程的创建和销毁所带来的性能开销.
  • 有效控制线程池中的最大并发数,避免大量线程之间因为相互抢占系统资源而导致的阻塞现象.
  • 能够对线程进行简单的管理,并提供定时执行和按照指定时间间隔循环执行等功能.
坚持原创技术分享,您的支持将鼓励我继续创作!