关于Android中的线程池

从概念到实现,再从应用到原理。关于线程池,你所应该知道的一切。

线程池简介

在操作系统中,线程时操作系统调度的最小单元。同时线程又是一种受限的系统资源,什么意思呢?就是说线程不可能无限制的产生,并且线程的创建和销毁的过程都会产生相应的开销。

当系统中存在大量的线程时,系统会通过时间片轮转的方式调度每个线程。因此线程不可能做到绝对的并行,除非线程数小于等于CPU的核心数,一般这是不太可能的。

如果我们在一个进程中,频繁地创建和销毁线程,显然不是一个高效的做法。线程池就是为了解决这一问题,应运而生。

线程池简单来说,就是一个缓存了一定数量线程的区域。

它的优点可以大致归纳为以下三点。

  1. 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。

  2. 能有效地控制线程的最大并发数,避免大量的线程因为互相抢占资源而导致的阻塞现象。

  3. 能够对线程进行简单的管理,并提供定时执行以及指定时间间隔循环执行等功能。

线程池的实现

Android中的线程池的概念,来源于Java中的Executor。Executor是一个接口,它的真正实现类是ThreadPoolExecutor,ThreadPoolExecutor的构造方法提供了一系列的参数来配置线程池。

1
2
3
4
5
6
public ThreadPoolExecutor (int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)

接下来,对这些参数逐一做简单的解释。

核心线程数(corePoolSize)

默认情况下,核心线程会在线程池中一直存活,即便它们属于闲置状态。

如果我们改变ThreadPoolExecutor的allowCoreThreadTimeOut属性,将其设置为true,那么核心线程在闲置时会有超时策略。当闲置核心线程的等待新任务时间超过某个时长后,核心线程就会被终止。这个时长由keepAliveTime指定。

最大线程数(maximumPoolSize)

当线程池的活动线程数达到最大线程数后,后续新的任务将会被阻塞。

超时时长(keepAliveTime)

非核心线程如果闲置时长超过这个超时时长,非核心线程就会被回收。

上面提到过,如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,核心线程同样也会被这个超时时长支配。

时间单位(unit)

这是超时时长参数的时间单位,为一个枚举。常见的有TimeUnit.MILLISECONDS、TimeUnit.SECONDS、TimeUnit.MINUTES等。

任务队列(workQueue)

通过线程池的execute方法提交的Runnable对象会储存在任务队列这个参数中。

线程工厂(threadFactory)

线程工厂为线程池提供创建新线程的功能。ThreadFactory是一个接口,它只有一个实现方法:Thread newThread(Runnable r)。

线程池的运行

ThreadPoolExecutor在执行任务时会大致遵循以下的规则。

  1. 如果线程池中的线程数量没有达到核心线程的数量,会直接创建一个核心线程来执行任务。

  2. 如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。

  3. 如果任务队列已满,将无法再继续插入。这时如果线程数量如果没有达到线程池规定的最大值,则立刻启动一个非核心线程来执行任务。

  4. 如果任务队列已满,而且线程数量已达到线程池规定的最大值,那么就拒绝执行此任务。ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。

线程池的运行流程

线程池的使用

线程池的用法如下。

创建线程池

创建时,通过配置线程池的参数,从而实现自己所需内存。下面以AsyncTask中的线程池为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static final int CPU_COUNT = Runtime.getRuntime().availableProcess();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;

private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
}

private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable> (128);

public static final Executor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
sPoolWorkQueue,
sThreadFactory);

我们可以很明显的看出线程池的配置规格。

  1. 核心线程数为 CPU核心数+1。

  2. 最大线程数为 CPU核心数的2倍+1。

  3. 超时时长为 1秒。

  4. 任务队列的容量为 128。

提交任务

通过execute()方法,传入Runnable对象,向线程池提交任务。

1
2
3
4
5
6
7
threadPool.execute(new Runnable() {
@Override
public void run() {
// 线程执行任务
...
}
});

关闭线程池

1
2
threadPool.shutdown();
//threadPool.shutdownNow()

此时,会遍历线程池中的所有工作线程,然后逐个调用线程的interrupt()方法中断线程。

shutdown(): 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程。

shutdownNow(): 设置线程池的状态为STOP,然后中断所有没有正在执行任务或暂停任务的线程。并返回等待任务的列表。

一般多以shutdown()关闭线程池。如果任务不一定要执行完,则以shutdownNow()关闭线程池。

常见线程池

Android中有四种最常见的线程池,它们都是通过配置好核心参数,来实现自己的功能特性。

定长线程池

定长线程池通过Executors.newFixedThreadPool()创建。

它的特性如下。

  1. 线程数量固定。
  2. 只有核心线程且无超时机制(不会被回收)。
  3. 任务队列无大小限制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建定长线程池并设置核心线程数量为5
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

// 创建好需执行的线程对象
Runnable task =new Runnable() {
public void run() {
System.out.println("run run run!");
}
};

// 提交任务
fixedThreadPool.execute(task);

// 关闭线程池
fixedThreadPool.shutdown();

它的特性意味着它能够更快速的响应外界的请求。我们可以通过它控制线程的最大并发数。

可缓存线程池

可缓存线程池通过Executors.newCachedThreadPool()创建。

它的特性如下。

  1. 只有非核心线程。
  2. 非核心线程数量无限制。
  3. 超时时长为60秒。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建可缓存线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

// 创建好需执行的线程对象
Runnable task =new Runnable() {
public void run() {
System.out.println("run run run!");
}
};

// 提交任务
cachedThreadPool.execute(task);

// 关闭线程池
fixedThreadPool.shutdown();

它的特性意味着任何任务到来都会立刻执行,不需要等待。比较适合执行大量的耗时少的任务。

定时线程池

定时线程池通过Executors.newScheduledThreadPool()创建。

它的特性如下。

  1. 核心线程数量固定。
  2. 非核心线程数量无限制。
  3. 超时时长为0,非核心线程闲置马上回收。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建定时线程池并设置核心线程数量为5
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

// 创建好需执行的线程对象
Runnable task =new Runnable() {
public void run() {
System.out.println("run run run!");
}
};

// 提交任务

// 延迟1s后执行
scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS);
// 延迟1000ms后每隔3000ms执行
scheduledThreadPool.scheduleAtFixedRate(task, 1000, 3000, TimeUnit.MILLISECONDS);

// 关闭线程池
scheduledThreadPool.shutdown();

它主要用于执行定时任务和具有固定周期的任务。

单线程化线程池

单线程化线程池通过Executors.newSingleThreadExecutor()创建。

它的特性如下。

  1. 只有一个核心线程。
  2. 确保所有任务都在一个线程中按顺序执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建单线程化线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

// 创建好需执行的线程对象
Runnable task =new Runnable() {
public void run() {
System.out.println("run run run!");
}
};

// 提交任务
singleThreadExecutor.execute(task);

// 关闭线程池
singleThreadExecutor.shutdown();

它能够统一所有的外界任务到一个线程中,使这些任务之间不需要处理线程同步的问题。

总结

好了,认真读完本文后,你已经对线程池的概念和用法了如指掌。接下来,就是根据项目需求,灵活地运用它们吧!

Chen wechat
欢迎扫描二维码,订阅我的博客公众号MiracleChen