在 Android 开发中,我们经常需要在后台执行耗时操作,以避免阻塞主线程(UI 线程),从而导致应用无响应(ANR)。传统的做法是使用 Thread
类,但 Thread
的管理开销较大,包括线程的创建、销毁和调度。当并发任务较多时,频繁地创建和销毁线程会消耗大量的系统资源,甚至可能导致 OOM(Out Of Memory)错误。
ExecutorService
接口(及其父接口 Executor
)是 Java 并发包 java.util.concurrent
中的核心组件,它提供了一种更高级、更灵活的方式来管理和执行并发任务。它将任务的提交和任务的执行解耦,允许我们使用线程池来管理线程,从而提高应用程序的性能和稳定性。
ExecutorService 概述
ExecutorService
是 Executor
的子接口,它提供了更丰富的生命周期管理方法,例如关闭线程池、判断线程池是否已关闭等。
核心思想:
- 线程池(Thread Pool):
ExecutorService
的实现通常基于线程池。线程池预先创建了一定数量的线程,当有任务提交时,线程池中的空闲线程会去执行任务。任务执行完毕后,线程不会被销毁,而是返回线程池等待下一个任务。这样就避免了频繁创建和销毁线程的开销。 - 任务提交与执行分离:开发者只需将任务提交给
ExecutorService
,而无需关心任务具体由哪个线程执行、线程如何管理等底层细节。 - 任务队列(Task Queue):当线程池中的所有线程都在忙碌时,新提交的任务会被放入一个任务队列中等待。
ExecutorService 的主要优点
- 资源管理:避免了线程频繁创建和销毁的开销,降低了系统资源消耗。
- 提高响应速度:当任务到达时,可以直接从线程池中获取线程执行,无需等待线程创建,从而提高了响应速度。
- 提高稳定性:限制了并发线程的数量,避免了过多的线程导致系统资源耗尽。
- 提供丰富的功能:例如定时任务、任务取消、任务结果获取等。
特性 / 工具 | Thread | ExecutorService | HandlerThread | Coroutine (协程) | RxJava | WorkManager |
---|---|---|---|---|---|---|
线程复用 | ❌ 每次新建 | ✅ 线程池复用 | ✅ 单线程长驻 | ✅ 调度器复用 | ✅ 调度器复用 | ✅ 系统级复用 |
是否并发处理任务 | ❌(需手动管理) | ✅ 高并发处理 | ❌ 顺序执行(消息队列) | ✅ 支持并发/顺序 | ✅ 支持高并发 | ✅(系统调度并发) |
任务调度能力 | ❌ 无队列 | ✅ 可配置队列/线程策略 | ✅ 消息队列 | ✅ Dispatcher 灵活切换 | ✅ .observeOn() 等灵活切换 | ✅ 系统控制、基于约束 |
主线程交互支持 | ❌ 需手动 | ❌ 需结合 Handler | ✅ 内置 Handler | ✅ withContext(Main) | ✅ .observeOn(Main) | ✅ 自动在主线程回调 |
生命周期感知 | ❌ | ❌ | ❌ | ✅ 可集成 ViewModelScope | ⚠️ 需手动处理 | ✅ 自动管理,生命周期无关 |
适合复杂流处理 | ❌ | ⚠️ 需手动拆分逻辑 | ❌ | ✅ 易组合、结构化优雅 | ✅ 响应式流天生适合 | ❌(偏系统任务,不适用于复杂流) |
取消任务机制 | ✅ interrupt() | ✅ Future.cancel() | ⚠️ 手动标记退出 | ✅ Job.cancel() | ✅ dispose() | ✅ 根据约束自动取消 |
适合后台持久任务 | ❌ | ✅ | ✅ | ✅(结合 IO Dispatcher ) | ✅ | ✅ 持久运行,系统可靠调度 |
Jetpack 兼容性 | ❌ | ⚠️ 一般 | ❌ | ✅ 完美集成 | ⚠️ 可与 LiveData 配合 | ✅ Jetpack 官方支持 |
推荐使用场景 | 简单线程任务 | 高并发异步任务 | 轮询/消息处理类后台任务 | ✅ 推荐主力工具,用于几乎所有异步任务 | 响应式编程、复杂事件流 | 后台约束型任务(上传/同步等) |
ExecutorService 的创建方法
Executors
工具类提供了多种静态工厂方法来创建不同类型的 ExecutorService
:
-
Executors.newFixedThreadPool(int nThreads)
:- 创建一个固定大小的线程池。
- 线程池中的线程数量是固定的,如果所有线程都在忙碌,新任务会进入一个无界队列等待。
- 适用于已知并发任务数量,并且希望控制并发线程数量的场景。
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5); // 创建一个包含5个线程的线程池
-
Executors.newCachedThreadPool()
:- 创建一个可缓存的线程池。
- 线程池的线程数量是不固定的,当有任务提交时,如果线程池中有空闲线程,则直接使用;如果没有空闲线程,则创建一个新线程来执行任务。
- 空闲的线程在一段时间(通常是 60 秒)后会被回收。
- 适用于执行大量短期异步任务的场景,可以根据任务量自动调整线程数量。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
-
Executors.newSingleThreadExecutor()
:- 创建一个单线程的
ExecutorService
。 - 它只有一个工作线程,所有的任务都按照提交的顺序串行执行。
- 适用于需要保证所有任务严格顺序执行的场景。
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
- 创建一个单线程的
-
Executors.newScheduledThreadPool(int corePoolSize)
:- 创建一个可定时执行任务的线程池。
- 它支持延迟执行任务和周期性执行任务。
corePoolSize
表示核心线程数量。
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
ExecutorService 的使用方法
ExecutorService
主要通过以下方法来提交任务:
-
execute(Runnable command)
:- 提交一个不需要返回结果的任务。
Runnable
接口的run()
方法不返回任何值。
fixedThreadPool.execute(new Runnable() { @Override public void run() { // 后台执行耗时操作 System.out.println("Executing task in fixedThreadPool..."); try { Thread.sleep(2000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Task in fixedThreadPool finished."); } });
-
submit(Callable<T> task)
:- 提交一个需要返回结果的任务。
Callable
接口的call()
方法可以返回一个值,并且可以抛出异常。submit()
方法会返回一个Future
对象,可以通过Future
对象获取任务执行结果。
Future<String> future = cachedThreadPool.submit(new Callable<String>() { @Override public String call() throws Exception { System.out.println("Executing task in cachedThreadPool..."); Thread.sleep(3000); return "Task result from cachedThreadPool"; } }); // 在需要结果的时候获取 try { String result = future.get(); // 阻塞直到任务完成并返回结果 System.out.println(result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); }
-
submit(Runnable task, T result)
:- 提交一个
Runnable
任务,并指定一个结果对象,当任务完成时,该结果对象会作为Future
的结果返回。 - 这个方法通常用于
Runnable
任务本身没有返回值,但你仍然希望通过Future
获取某个特定对象的情况。
String myResult = "Default Result"; Future<String> futureRunnable = singleThreadPool.submit(new Runnable() { @Override public void run() { System.out.println("Executing Runnable with result in singleThreadPool..."); // do something } }, myResult); try { String finalResult = futureRunnable.get(); System.out.println("Runnable with result finished: " + finalResult); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); }
- 提交一个
-
submit(Runnable task)
:- 提交一个
Runnable
任务。 - 虽然
Runnable
没有返回值,但submit()
方法仍然会返回一个Future<?>
对象,可以用于检查任务是否完成或取消任务。当任务成功完成时,调用Future.get()
会返回null
。
Future<?> futureVoid = fixedThreadPool.submit(new Runnable() { @Override public void run() { System.out.println("Executing void task in fixedThreadPool..."); try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); try { futureVoid.get(); // 阻塞直到任务完成,返回null System.out.println("Void task finished."); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); }
- 提交一个
ScheduledExecutorService 的使用方法
ScheduledExecutorService
提供了定时和周期性任务执行的能力:
-
schedule(Callable<V> callable, long delay, TimeUnit unit)
:- 在指定的延迟时间后执行一次任务。
scheduledThreadPool.schedule(new Callable<String>() { @Override public String call() throws Exception { System.out.println("Scheduled task executed after 5 seconds."); return "Scheduled Task Done"; } }, 5, TimeUnit.SECONDS);
-
schedule(Runnable command, long delay, TimeUnit unit)
:- 在指定的延迟时间后执行一次任务(无返回值)。
scheduledThreadPool.schedule(new Runnable() { @Override public void run() { System.out.println("Another scheduled runnable executed after 3 seconds."); } }, 3, TimeUnit.SECONDS);
-
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
:- 在指定的初始延迟后开始执行任务,然后以固定的频率(
period
)周期性地执行任务。 period
是指任务开始执行的间隔时间,不考虑任务本身的执行时间。如果任务执行时间超过period
,则下一个任务会立即开始执行(或者在当前任务完成后立即执行)。
scheduledThreadPool.scheduleAtFixedRate(new Runnable() { @Override public void run() { long now = System.currentTimeMillis(); System.out.println("Fixed rate task executed at: " + now); try { Thread.sleep(1500); // 假设任务执行1.5秒 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }, 1, 2, TimeUnit.SECONDS); // 首次延迟1秒,之后每2秒执行一次
- 在指定的初始延迟后开始执行任务,然后以固定的频率(
-
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
:- 在指定的初始延迟后开始执行任务,然后在上一个任务执行完成后再等待指定的
delay
时间后执行下一个任务。 - 这种方式可以保证任务之间至少有
delay
的间隔。
scheduledThreadPool.scheduleWithFixedDelay(new Runnable() { @Override public void run() { long now = System.currentTimeMillis(); System.out.println("Fixed delay task executed at: " + now); try { Thread.sleep(1500); // 假设任务执行1.5秒 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }, 1, 2, TimeUnit.SECONDS); // 首次延迟1秒,之后在上一个任务完成后再等待2秒执行下一个
- 在指定的初始延迟后开始执行任务,然后在上一个任务执行完成后再等待指定的
ExecutorService 的关闭
在使用完 ExecutorService
后,务必将其关闭,否则线程池中的线程会一直保持活跃状态,阻止应用程序正常退出,造成资源泄露。
-
shutdown()
:- 平滑地关闭
ExecutorService
。 - 它不会立即停止正在执行的任务,也不会接受新任务。
- 已提交但尚未执行的任务会继续执行。
- 当所有任务执行完成后,线程池才会真正关闭。
fixedThreadPool.shutdown();
- 平滑地关闭
-
shutdownNow()
:- 尝试立即停止所有正在执行的任务。
- 它会中断正在执行的任务,并返回一个尚未执行的任务列表。
- 它不会等待任务完成。
- 这是一个“尽力而为”的操作,不保证所有任务都会立即停止。
List<Runnable> unexecutedTasks = cachedThreadPool.shutdownNow(); System.out.println("Unexecuted tasks: " + unexecutedTasks.size());
-
awaitTermination(long timeout, TimeUnit unit)
:- 在调用
shutdown()
后,可以使用此方法等待线程池终止。 - 它会阻塞当前线程,直到所有任务执行完成,或者达到指定的超时时间,或者当前线程被中断。
- 通常和
shutdown()
配合使用,以确保在所有任务完成后再进行后续操作。
fixedThreadPool.shutdown(); try { if (!fixedThreadPool.awaitTermination(60, TimeUnit.SECONDS)) { // 如果在指定时间内未能终止,可以再次尝试 shutdownNow() 或者记录日志 System.err.println("Thread pool did not terminate in the specified time."); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.err.println("Waiting for thread pool termination was interrupted."); }
- 在调用
ExecutorService 在 Android 中的最佳实践
示例场景
假设我们有一个 TextView
需要每 5 秒显示一次从“网络”或“数据库”获取的模拟数据。
1. 准备你的 Android 项目
-
activity_main.xml
(布局文件):<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center"> <TextView android:id="@+id/dataTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="等待数据刷新..." android:textSize="24sp" android:textColor="@android:color/black" /> </LinearLayout>
-
MyApp.java
(Application 类,用于管理全局的 ExecutorService): (沿用之前我们讨论过的 Application 类,确保 ExecutorService 是单例且在应用生命周期内正确管理)// MyApp.java (保持不变,已包含在之前的回答中) import android.app.Application; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class MyApp extends Application { private static volatile ExecutorService backgroundExecutor; private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int CORE_POOL_SIZE = CPU_COUNT * 2; @Override public void onCreate() { super.onCreate(); // 创建线程池 backgroundExecutor = Executors.newFixedThreadPool(CORE_POOL_SIZE); } public static ExecutorService getBackgroundExecutor() { if (backgroundExecutor == null) { synchronized (MyApp.class) { if (backgroundExecutor == null) { backgroundExecutor = Executors.newFixedThreadPool(CORE_POOL_SIZE); } } } return backgroundExecutor; } @Override public void onTerminate() { super.onTerminate(); if (backgroundExecutor != null && !backgroundExecutor.isShutdown()) { backgroundExecutor.shutdown(); try { if (!backgroundExecutor.awaitTermination(60, TimeUnit.SECONDS)) { backgroundExecutor.shutdownNow(); if (!backgroundExecutor.awaitTermination(60, TimeUnit.SECONDS)) { System.err.println("ExecutorService did not terminate after forced shutdown."); } } } catch (InterruptedException ie) { backgroundExecutor.shutdownNow(); Thread.currentThread().interrupt(); } } } }
2. MainActivity.java
(核心逻辑)
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
public class MainActivity extends AppCompatActivity {
private TextView dataTextView;
private Handler uiHandler; // 用于将结果发布到主线程
private ExecutorService backgroundExecutor; // 获取后台线程池
private final long REFRESH_INTERVAL_MS = 5000; // 刷新间隔:5 秒
// 这个 Runnable 封装了“获取数据”和“调度下一次刷新”的逻辑。
// 它首先将数据获取任务提交给 ExecutorService,然后等待 ExecutorService 完成并更新 UI,
// 最后再通过 Handler 调度自身的下一次执行。
private final Runnable refreshDataAndScheduleNext = new Runnable() {
@Override
public void run() {
// 将数据获取任务提交给 ExecutorService
backgroundExecutor.execute(new Runnable() {
@Override
public void run() {
// --- 1. 后台线程执行耗时的数据获取操作 ---
String fetchedData = fetchDataInBackground();
System.out.println("后台获取数据完成: " + fetchedData);
// --- 2. 通过 Handler 将结果发布到主线程更新 UI ---
uiHandler.post(new Runnable() {
@Override
public void run() {
dataTextView.setText(fetchedData);
System.out.println("UI已更新: " + fetchedData);
}
});
}
});
// --- 3. 再次调度此 Runnable,实现定时循环 ---
// 每次任务完成后,通过 Handler 再次 postDelayed 自身,实现周期性执行。
// 注意:这里是主线程的Handler在调度下一个后台任务的启动。
uiHandler.postDelayed(this, REFRESH_INTERVAL_MS);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dataTextView = findViewById(R.id.dataTextView);
// 初始化主线程 Handler
uiHandler = new Handler(Looper.getMainLooper());
// 获取全局的后台线程池
backgroundExecutor = MyApp.getBackgroundExecutor();
}
@Override
protected void onResume() {
super.onResume();
// 在 Activity 可见时启动定时刷新
// 第一次调度任务,之后任务会通过自身递归实现循环
uiHandler.postDelayed(refreshDataAndScheduleNext, REFRESH_INTERVAL_MS);
System.out.println("定时UI刷新已启动.");
}
@Override
protected void onPause() {
super.onPause();
// 在 Activity 不可见时停止定时刷新,避免内存泄漏和不必要的资源消耗
uiHandler.removeCallbacks(refreshDataAndScheduleNext);
System.out.println("定时UI刷新已停止.");
}
@Override
protected void onDestroy() {
super.onDestroy();
// 再次确保移除所有回调,防止内存泄漏
uiHandler.removeCallbacks(refreshDataAndScheduleNext);
// 通常不需要在这里关闭 backgroundExecutor,因为它是 Application 级别的
// 它的关闭逻辑应该在 MyApp.onTerminate() 中处理
}
/**
* 模拟在后台线程中执行的数据获取操作。
* 这是一个耗时操作。
* @return 模拟的最新数据字符串
*/
private String fetchDataInBackground() {
try {
// 模拟网络请求或数据库查询的耗时
Thread.sleep(2000); // 假设数据获取需要2秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "数据获取被中断";
}
// 生成模拟数据,例如当前时间
return "新数据: " + new SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(new Date());
}
}
工作流程解释:
-
MyApp.java
(ExecutorService
的创建和管理):- 在
Application
类的onCreate()
中创建并初始化一个全局的ExecutorService
(如FixedThreadPool
)。这确保了整个应用只使用一个线程池实例,方便管理和资源复用。 - 提供一个静态方法
getBackgroundExecutor()
让其他组件可以获取到这个线程池实例。 - 在
onTerminate()
中,优雅地关闭ExecutorService
,释放所有线程资源。
- 在
-
MainActivity.java
(Handler
和调度逻辑):-
uiHandler = new Handler(Looper.getMainLooper());
: 在onCreate()
中初始化Handler
,并明确它关联到主线程的 Looper,这意味着通过此Handler
发布的所有任务都将在主线程中执行。 -
backgroundExecutor = MyApp.getBackgroundExecutor();
: 获取全局的后台线程池实例。 -
refreshDataAndScheduleNext
(Runnable) :-
这是一个关键的
Runnable
当它被执行时,它会做两件事:- 将数据获取任务提交到
backgroundExecutor
:backgroundExecutor.execute(new Runnable() { ... });
。这个内部的Runnable
负责执行fetchDataInBackground()
这个耗时操作。 - 在数据获取完成后,通过
uiHandler.post()
将结果发布到主线程:uiHandler.post(new Runnable() { ... });
。这确保了TextView.setText()
(UI 操作)是在主线程上安全进行的。 - 重新调度自身:
uiHandler.postDelayed(this, REFRESH_INTERVAL_MS);
。在当前循环的 UI 更新操作(或数据获取任务的提交)完成后,它会再次通过主线程的Handler
调度自身,使其在REFRESH_INTERVAL_MS
毫秒后再次执行,从而形成一个定时刷新的循环。
- 将数据获取任务提交到
-
-
onResume()
和onPause()
:- 在
onResume()
中,调用uiHandler.postDelayed(refreshDataAndScheduleNext, REFRESH_INTERVAL_MS);
来启动第一次定时刷新。 - 在
onPause()
中,调用uiHandler.removeCallbacks(refreshDataAndScheduleNext);
来停止定时刷新。这至关重要,它可以防止内存泄漏(因为Runnable
持有对 Activity 的引用)和不必要的后台活动。
- 在
-
这种组合方式实现了:
- UI 响应性:所有耗时操作都在
ExecutorService
提供的后台线程中执行,主线程始终保持流畅,避免 ANR。 - UI 安全更新:通过
Handler
的post()
方法,确保所有 UI 更新都回到主线程执行。 - 资源高效利用:
ExecutorService
通过线程池管理线程,避免了频繁创建和销毁线程的开销。 - 定时调度:
Handler
的postDelayed()
提供了简单可靠的定时调度机制。 - 生命周期管理:结合 Activity 的
onResume()
和onPause()
来启动和停止刷新,避免内存泄漏和资源浪费。
注意事项
-
合理设置线程池大小
太小会导致任务排队,太大可能造成资源浪费甚至 OOM。 -
记得调用
shutdown()
否则线程池不会自动退出,可能导致程序无法正常结束。 -
异常处理要小心
如果任务中抛出未捕获的异常,可能导致任务失败而无声无息。 -
避免死锁
不要在任务中调用future.get()
而导致线程阻塞。
Android 中的应用
在 Android 开发中,ExecutorService
常用于:
- 异步加载图片或数据(替代 AsyncTask)
- 执行数据库操作(Room、SQLite)
- 多文件下载、上传
- 定时刷新 UI 数据(结合 Handler)
总结
ExecutorService
是 Android 开发中处理并发任务的强大工具。通过合理使用线程池,可以有效管理线程资源,提高应用程序的性能、响应速度和稳定性。理解不同类型的 ExecutorService
及其使用场景,以及如何正确地提交、取消和关闭任务,是编写高效、健壮 Android 应用的关键。