精通Android线程池的必备高级技巧

2023-11-27  本文已影响0人  BlueSocks

在Android开发中,我们经常会遇到需要执行耗时操作的情况,例如网络请求、数据库读写、图片加载等。为了避免这些操作阻塞主线程,我们通常会使用线程池来管理并发执行任务。而Android Executors是一个非常常用的线程池管理工具。本文将深入解析Android Executors的原理,帮助大家更好地理解和使用这个工具。

Android线程池简介

在移动应用中,频繁地创建和销毁线程可能导致系统资源的浪费。线程池通过维护一组可重用的线程,降低了线程创建和销毁的开销。这种机制使得线程的使用更加高效,同时能够控制并发线程的数量,防止系统资源被过度占用。

线程池的作用和优势

Executors框架概述

java.util.concurrent.Executors是Java中用于创建线程池的工厂类。它提供了一系列静态方法,可以方便地创建不同类型的线程池。这些线程池类型包括CachedThreadPool、FixedThreadPool、ScheduledThreadPool和SingleThreadExecutor等。

通过使用Executors,可以简化线程池的创建过程,专注于任务的实现和调度。

Executors工厂方法

Executors类提供了几个常用的工厂方法:

任务提交和执行

一旦线程池创建完成,可以通过将任务提交给线程池来执行。任务可以是实现了Runnable接口的普通线程,也可以是实现了Callable接口的带返回值的线程。

ExecutorService executorService = Executors.newFixedThreadPool(5);

executorService.submit(() -> {
    // 执行任务逻辑
});

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

线程池有一个生命周期,它包括创建、运行和关闭三个阶段。创建后线程池可以接受并执行任务,一旦不再需要,可以通过调用shutdown()shutdownNow()方法来关闭线程池。

ThreadPoolExecutor

ThreadPoolExecutorjava.util.concurrent包中的核心类之一,用于实现自定义的线程池。理解ThreadPoolExecutor的工作原理对于深入掌握线程池的使用至关重要。

核心参数解释

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

ThreadPoolExecutor的构造方法包括多个参数,其中一些是线程池的核心参数:

线程池的生命周期和状态转换

线程池具有不同的生命周期状态,包括RUNNINGSHUTDOWNSTOPTERMINATED

线程池的状态转换是受到任务提交、关闭和线程池终止等事件的影响的。

拒绝策略

线程池的拒绝策略决定了当线程池无法执行提交的任务时的行为。常见的拒绝策略包括AbortPolicyCallerRunsPolicyDiscardPolicyDiscardOldestPolicy等。

AbortPolicy

AbortPolicy是默认的拒绝策略,当线程池无法接受新任务时,会抛出RejectedExecutionException异常。

public static class AbortPolicy implements RejectedExecutionHandler {
    
    public AbortPolicy() { }

    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}

CallerRunsPolicy

CallerRunsPolicy拒绝策略会直接在提交任务的线程中执行被拒绝的任务。这种策略适用于任务的负载比较轻,而且执行时间短暂的情况。

public static class CallerRunsPolicy implements RejectedExecutionHandler {

    public CallerRunsPolicy() { }

    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }
}

DiscardPolicy

DiscardPolicy拒绝策略会默默地丢弃无法处理的任务,不提供任何反馈。如果任务队列已满,新提交的任务会被直接丢弃。

public static class DiscardPolicy implements RejectedExecutionHandler {

    public DiscardPolicy() { }

    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    }
}

DiscardOldestPolicy

DiscardOldestPolicy拒绝策略会丢弃队列中最早被提交但尚未被执行的任务,然后将新任务加入队列。

public static class DiscardOldestPolicy implements RejectedExecutionHandler {

    public DiscardOldestPolicy() { }

    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

也可以使用setRejectedExecutionHandler()方法设置拒绝策略,处理线程池无法接受新任务时的行为。或者实现RejectedExecutionHandler接口定义自己的策略。

四种内置线程池类型

Executors类中,提供了四种内置的线程池类型,分别是CachedThreadPoolFixedThreadPoolScheduledThreadPoolSingleThreadExecutor。每种线程池类型都适用于不同的场景,下面我们将详细介绍每一种类型的特点和适用情况。

CachedThreadPool

FixedThreadPool

ScheduledThreadPool

SingleThreadExecutor

阻塞队列的选择与优化

线程池中的阻塞队列对于任务的存储和调度起着重要作用。不同的阻塞队列实现对线程池的性能和行为产生直接影响。以下是一些常见的阻塞队列以及它们的选择与优化建议。

LinkedBlockingQueue

LinkedBlockingQueue是一个基于链表的阻塞队列,可以选择不设置容量(默认为Integer.MAX_VALUE)。

ArrayBlockingQueue

ArrayBlockingQueue是一个基于数组的有界阻塞队列,必须设置容量。

SynchronousQueue

SynchronousQueue是一个没有容量的阻塞队列,每个插入操作必须等待另一个线程的移除操作。

PriorityBlockingQueue

PriorityBlockingQueue是一个支持优先级的无界阻塞队列。

自定义线程池

除了使用内置的线程池类型外,ThreadPoolExecutor还允许开发者自定义线程池,以满足特定的业务需求。自定义线程池的步骤如下:

创建ThreadPoolExecutor实例

通过ThreadPoolExecutor的构造方法,设置核心线程数、最大线程数、阻塞队列等参数来创建线程池实例。

// 创建一个自定义的线程池
ThreadPoolExecutor customThreadPool = new ThreadPoolExecutor(
    corePoolSize,
    maximumPoolSize,
    keepAliveTime,
    timeUnit,
    workQueue,
    threadFactory,
    handler
);

配置ThreadFactory

通过setThreadFactory()方法配置线程工厂,用于创建新的线程。可以使用Executors.defaultThreadFactory()创建默认线程工厂,也可以实现ThreadFactory接口自定义线程创建过程。

// 自定义线程工厂
ThreadFactory customThreadFactory = new ThreadFactory() {
    private final AtomicInteger threadNumber = new AtomicInteger(1);

    @Override
    public Thread newThread(Runnable r) {
        return new Thread(r, "CustomThread-" + threadNumber.getAndIncrement());
    }
};

customThreadPool.setThreadFactory(customThreadFactory);

配置Handler

通过setThreadFactory()方法配置RejectedExecutionHandler,用于处理被拒绝的任务。可以选择使用预定义的处理器,如AbortPolicyCallerRunsPolicy等,也可以实现RejectedExecutionHandler接口定义自己的处理逻辑。

// 自定义拒绝策略处理器
RejectedExecutionHandler customHandler = (r, executor) -> {
    // 处理被拒绝的任务,例如记录日志或其他处理
    System.out.println("Task Rejected: " + r.toString());
};

customThreadPool.setRejectedExecutionHandler(customHandler);

提交任务

通过调用execute()submit()方法将任务提交给自定义的线程池执行。

customThreadPool.execute(() -> {
    // 执行任务逻辑
});

线程池的注意事项

在使用线程池时,以下注意事项可以充分发挥线程池的优势,提高应用性能和稳定性。

合理设置线程池参数

检测和处理异常

在任务的run方法中要注意捕获异常,以避免异常抛出导致线程终止。可以在Thread.UncaughtExceptionHandler中处理未捕获的异常。

Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
    // 处理未捕获的异常
});

使用Callable获取任务执行结果

如果任务需要返回结果,可以使用Callable接口,通过Future对象获取任务的执行结果。

ExecutorService executorService = Executors.newFixedThreadPool(5);
Future<Integer> future = executorService.submit(() -> {
    // 执行任务逻辑
    return 42;
});

// 获取任务执行结果
int result = future.get();

避免线程泄漏

线程泄漏是在使用线程池时需要注意的一个问题。下面是一些避免线程泄漏的方法:

监控线程池状态

监控线程池的状态可以帮助我们及时发现线程池的异常和性能问题。下面是一些方法来监控线程池的状态:

总结

通过本文的学习,我们深入了解了Android中线程池Executors的重要性和灵活性。线程池作为一种多线程管理机制,在Android应用中发挥着关键作用,能够有效提高应用的性能、响应速度,同时避免过度占用系统资源。

上一篇下一篇

猜你喜欢

热点阅读