Android中ExecutorService介绍及其使用方法

在 Android 开发中,我们经常需要在后台执行耗时操作,以避免阻塞主线程(UI 线程),从而导致应用无响应(ANR)。传统的做法是使用 Thread 类,但 Thread 的管理开销较大,包括线程的创建、销毁和调度。当并发任务较多时,频繁地创建和销毁线程会消耗大量的系统资源,甚至可能导致 OOM(Out Of Memory)错误。

ExecutorService 接口(及其父接口 Executor)是 Java 并发包 java.util.concurrent 中的核心组件,它提供了一种更高级、更灵活的方式来管理和执行并发任务。它将任务的提交和任务的执行解耦,允许我们使用线程池来管理线程,从而提高应用程序的性能和稳定性。

ExecutorService 概述

ExecutorServiceExecutor 的子接口,它提供了更丰富的生命周期管理方法,例如关闭线程池、判断线程池是否已关闭等。

核心思想:

  • 线程池(Thread Pool)ExecutorService 的实现通常基于线程池。线程池预先创建了一定数量的线程,当有任务提交时,线程池中的空闲线程会去执行任务。任务执行完毕后,线程不会被销毁,而是返回线程池等待下一个任务。这样就避免了频繁创建和销毁线程的开销。
  • 任务提交与执行分离:开发者只需将任务提交给 ExecutorService,而无需关心任务具体由哪个线程执行、线程如何管理等底层细节。
  • 任务队列(Task Queue):当线程池中的所有线程都在忙碌时,新提交的任务会被放入一个任务队列中等待。
    在这里插入图片描述

ExecutorService 的主要优点

  1. 资源管理:避免了线程频繁创建和销毁的开销,降低了系统资源消耗。
  2. 提高响应速度:当任务到达时,可以直接从线程池中获取线程执行,无需等待线程创建,从而提高了响应速度。
  3. 提高稳定性:限制了并发线程的数量,避免了过多的线程导致系统资源耗尽。
  4. 提供丰富的功能:例如定时任务、任务取消、任务结果获取等。
特性 / 工具ThreadExecutorServiceHandlerThreadCoroutine(协程)RxJavaWorkManager
线程复用❌ 每次新建✅ 线程池复用✅ 单线程长驻✅ 调度器复用✅ 调度器复用✅ 系统级复用
是否并发处理任务❌(需手动管理)✅ 高并发处理❌ 顺序执行(消息队列)✅ 支持并发/顺序✅ 支持高并发✅(系统调度并发)
任务调度能力❌ 无队列✅ 可配置队列/线程策略✅ 消息队列Dispatcher 灵活切换.observeOn() 等灵活切换✅ 系统控制、基于约束
主线程交互支持❌ 需手动❌ 需结合 Handler✅ 内置 HandlerwithContext(Main).observeOn(Main)✅ 自动在主线程回调
生命周期感知✅ 可集成 ViewModelScope⚠️ 需手动处理✅ 自动管理,生命周期无关
适合复杂流处理⚠️ 需手动拆分逻辑✅ 易组合、结构化优雅✅ 响应式流天生适合❌(偏系统任务,不适用于复杂流)
取消任务机制interrupt()Future.cancel()⚠️ 手动标记退出Job.cancel()dispose()✅ 根据约束自动取消
适合后台持久任务✅(结合 IO Dispatcher✅ 持久运行,系统可靠调度
Jetpack 兼容性⚠️ 一般✅ 完美集成⚠️ 可与 LiveData 配合✅ Jetpack 官方支持
推荐使用场景简单线程任务高并发异步任务轮询/消息处理类后台任务✅ 推荐主力工具,用于几乎所有异步任务响应式编程、复杂事件流后台约束型任务(上传/同步等)

ExecutorService 的创建方法

Executors 工具类提供了多种静态工厂方法来创建不同类型的 ExecutorService

  1. Executors.newFixedThreadPool(int nThreads)

    • 创建一个固定大小的线程池。
    • 线程池中的线程数量是固定的,如果所有线程都在忙碌,新任务会进入一个无界队列等待。
    • 适用于已知并发任务数量,并且希望控制并发线程数量的场景。
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5); // 创建一个包含5个线程的线程池
    
  2. Executors.newCachedThreadPool()

    • 创建一个可缓存的线程池。
    • 线程池的线程数量是不固定的,当有任务提交时,如果线程池中有空闲线程,则直接使用;如果没有空闲线程,则创建一个新线程来执行任务。
    • 空闲的线程在一段时间(通常是 60 秒)后会被回收。
    • 适用于执行大量短期异步任务的场景,可以根据任务量自动调整线程数量。
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    
  3. Executors.newSingleThreadExecutor()

    • 创建一个单线程的 ExecutorService
    • 它只有一个工作线程,所有的任务都按照提交的顺序串行执行。
    • 适用于需要保证所有任务严格顺序执行的场景。
    ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
    
  4. Executors.newScheduledThreadPool(int corePoolSize)

    • 创建一个可定时执行任务的线程池。
    • 它支持延迟执行任务和周期性执行任务。
    • corePoolSize 表示核心线程数量。
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
    

ExecutorService 的使用方法

ExecutorService 主要通过以下方法来提交任务:

  1. 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.");
        }
    });
    
  2. 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();
    }
    
  3. 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();
    }
    
  4. 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 提供了定时和周期性任务执行的能力:

  1. 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);
    
  2. 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);
    
  3. 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秒执行一次
    
  4. 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 后,务必将其关闭,否则线程池中的线程会一直保持活跃状态,阻止应用程序正常退出,造成资源泄露。

  1. shutdown()

    • 平滑地关闭 ExecutorService
    • 它不会立即停止正在执行的任务,也不会接受新任务。
    • 已提交但尚未执行的任务会继续执行。
    • 当所有任务执行完成后,线程池才会真正关闭。
    fixedThreadPool.shutdown();
    
  2. shutdownNow()

    • 尝试立即停止所有正在执行的任务。
    • 它会中断正在执行的任务,并返回一个尚未执行的任务列表。
    • 它不会等待任务完成。
    • 这是一个“尽力而为”的操作,不保证所有任务都会立即停止。
    List<Runnable> unexecutedTasks = cachedThreadPool.shutdownNow();
    System.out.println("Unexecuted tasks: " + unexecutedTasks.size());
    
  3. 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());
    }
}
工作流程解释:
  1. MyApp.java (ExecutorService 的创建和管理):

    • Application 类的 onCreate() 中创建并初始化一个全局的 ExecutorService(如 FixedThreadPool)。这确保了整个应用只使用一个线程池实例,方便管理和资源复用。
    • 提供一个静态方法 getBackgroundExecutor() 让其他组件可以获取到这个线程池实例。
    • onTerminate() 中,优雅地关闭 ExecutorService,释放所有线程资源。
  2. MainActivity.java (Handler 和调度逻辑):

    • uiHandler = new Handler(Looper.getMainLooper());: 在 onCreate() 中初始化 Handler,并明确它关联到主线程的 Looper,这意味着通过此 Handler 发布的所有任务都将在主线程中执行。

    • backgroundExecutor = MyApp.getBackgroundExecutor();: 获取全局的后台线程池实例。

    • refreshDataAndScheduleNext (Runnable) :

      • 这是一个关键的 Runnable 当它被执行时,它会做两件事:

        1. 将数据获取任务提交到 backgroundExecutorbackgroundExecutor.execute(new Runnable() { ... });。这个内部的 Runnable 负责执行 fetchDataInBackground() 这个耗时操作。
        2. 在数据获取完成后,通过 uiHandler.post() 将结果发布到主线程uiHandler.post(new Runnable() { ... });。这确保了 TextView.setText()(UI 操作)是在主线程上安全进行的。
        3. 重新调度自身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 安全更新:通过 Handlerpost() 方法,确保所有 UI 更新都回到主线程执行。
  • 资源高效利用ExecutorService 通过线程池管理线程,避免了频繁创建和销毁线程的开销。
  • 定时调度HandlerpostDelayed() 提供了简单可靠的定时调度机制。
  • 生命周期管理:结合 Activity 的 onResume()onPause() 来启动和停止刷新,避免内存泄漏和资源浪费。

注意事项

  1. 合理设置线程池大小
    太小会导致任务排队,太大可能造成资源浪费甚至 OOM。

  2. 记得调用 shutdown()
    否则线程池不会自动退出,可能导致程序无法正常结束。

  3. 异常处理要小心
    如果任务中抛出未捕获的异常,可能导致任务失败而无声无息。

  4. 避免死锁
    不要在任务中调用 future.get() 而导致线程阻塞。


Android 中的应用

在 Android 开发中,ExecutorService 常用于:

  • 异步加载图片或数据(替代 AsyncTask)
  • 执行数据库操作(Room、SQLite)
  • 多文件下载、上传
  • 定时刷新 UI 数据(结合 Handler)

总结

ExecutorService 是 Android 开发中处理并发任务的强大工具。通过合理使用线程池,可以有效管理线程资源,提高应用程序的性能、响应速度和稳定性。理解不同类型的 ExecutorService 及其使用场景,以及如何正确地提交、取消和关闭任务,是编写高效、健壮 Android 应用的关键。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值