【Android 异步操作】线程池 ( 线程池作用 | 线程池种类 | 线程池工作机制 | 线程池任务调度源码解析 )

简介: 【Android 异步操作】线程池 ( 线程池作用 | 线程池种类 | 线程池工作机制 | 线程池任务调度源码解析 )

文章目录

一、线程池作用

二、线程池种类

三、线程池工作机制

四、线程池任务调度源码解析





一、线程池作用


线程池作用 :


① 避免创建线程 : 避免每次使用线程时 , 都需要 创建线程对象 ;


② 统一管理 : 统一管理线程 , 重用存在的线程 , 减少线程对象创建 , 销毁的开销 ;


③ 控制并发 : 可 控制线程的最大并发数 , 提高资源使用效率 , 避免资源竞争导致堵塞 ;






二、线程池种类


线程池种类 :


① newCachedThreadPool : 可缓存线程池 , 如果 线程池线程个数已满 , 回收空闲线程 , 如果没有空闲线程 , 此时会创建新线程 ;


② newFixedThreadPool : 创建固定大小线程池 , 可设置并发数 , 如果并发数已满 , 后续任务会 在 等待队列 中等待可用线程 ;


③ newScheduledThreadPool : 创建固定大小线程池 , 其支持 周期性任务 ;


④ newSingleThreadExecutor : 创建 单线程 线程池 , 该线程池中 只有一个线程 , 所有的任务按照指定的优先级顺序执行 , 如 FIFO 先入先出 ( 先到的先执行 , 后到的后执行 ) , LIFO 后入先出 ( 后到的先执行 ) ;






三、线程池工作机制


线程池线程相关概念:


线程数 : 线程池的 有 最大线程数 MaxSzie , 核心线程数 CoreSize , 非核心线程数就是 MaxSize - CoreSize ;


示例 : 最大线程数 ( MaxSize ) 是 8 个 , 有 3 个核心线程 ( CoreSize ) , 5 个非核心线程 ;


非核心线程 : 闲置超过一定时间 , 就会被回收 ;



线程池任务调度 : 线程池中维护了一个任务队列 , 线程池启动后 , 会不停的从任务队列中取出任务 , 如果有新任务 , 执行如下操作 ;


如果 线程数 小于核心线程数 ( CoreSize ) , 那么创建核心线程 , 执行上述任务 ;


如果 线程数 大于核心线程数 ( CoreSize ) , 小于最大线程数 ( MaxSize ) , 那么创建非核心线程 , 执行上述任务 ;


如果 线程数 超过 最大线程数 ( MaxSize )


如果 任务队列没满 , 则将任务放入任务队列 ;

如果 任务队列满了 , 则抛出异常 ; 这里一般情况下需要手动处理这种情况 , 任务拒绝后 , 处理善后 ;






四、线程池任务调度源码解析


在 AsyncTask.java 中 , 在静态代码块中 , 自己 自定义创建了线程池 , 没有使用上述四种线程池 ;



创建线程池时传入的参数 :


CORE_POOL_SIZE : 核心线程数

MAXIMUM_POOL_SIZE : 最大线程数

KEEP_ALIVE_SECONDS : 闲置时间 , 非核心线程一旦闲置超过一定时间 , 就会被回收

TimeUnit.SECONDS : 闲置时间单位 , 秒

sPoolWorkQueue : 线程队列 , 任务队列

sThreadFactory : 线程工厂 , 用于生产线程

public abstract class AsyncTask<Params, Progress, Result> {
    static {
        /**
         * 自定义的线程池 : 
         * CORE_POOL_SIZE : 核心线程数 
         * MAXIMUM_POOL_SIZE : 最大线程数 
         * KEEP_ALIVE_SECONDS : 闲置时间 , 非核心线程一旦闲置超过一定时间 , 就会被回收
         * TimeUnit.SECONDS : 闲置时间单位 , 秒
         * sPoolWorkQueue : 线程队列 , 任务队列 
         * sThreadFactory : 线程工厂 , 用于生产线程 
         */  
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }
    private static class SerialExecutor implements Executor {
        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
              // 线程池执行任务 
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }
}


在 AsyncTask 中 , 调用 ThreadPoolExecutor THREAD_POOL_EXECUTOR 线程池的 void execute(Runnable command) 方法 , 执行线程池任务 ;


在 execute 方法中, 需要执行以下三个步骤 :


如果当前 运行线程数小于核心线程数 , 尝试 启动新线程执行该任务, 该任务是线程的第一个任务.调用 addWorker 方法会检查运行状态, 和线程运行个数, 避免在不应该添加线程时执行错误操作.


如果 任务成功加入队列, 需要 双重检查 ( 进入该方法后, 线程池可能关闭 ), 在进入该方法后, 是否添加了一个线程, 或者线程池是否关闭. 因此, 我们应该再次检查运行状态, 如果需要, 将任务放回队列中, 或者启动一个新线程.


如果 不能将任务入队, 尽量添加一个新线程. 如果添加失败, 此时线程池可能关闭, 或者运行线程数等于最大线程数, 需要拒绝该任务.


public class ThreadPoolExecutor extends AbstractExecutorService {
    /**
     * 在将来的某个时间执行给定的任务. 
     * 该任务可能在一个新线程中执行, 也可能在当前线程池中已存在的线程中执行.
     * 
     * 如果任务不能被提交执行, 或该线程池失效, 或该线程池线程个数由于超过最大线程数,
     * 任务被 RejectedExecutionHandler 处理. 
     *
     * @param command 向线程池中提交的任务 
     * @throws RejectedExecutionException 如果任务不能被接受, 抛出该异常
     * @throws NullPointerException 如果任务为空, 抛出该异常 
     */
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * 三个步骤:
         *
         * 1. 如果当前运行线程数小于核心线程数 , 尝试启动新线程执行该任务, 该任务是线程的第一个任务.
         * 调用 addWorker 方法会检查运行状态, 和线程运行个数, 避免在不应该添加线程时执行错误操作.
         *
         * 2. 如果任务成功加入队列, 需要双重检查 ( 进入该方法后, 线程池可能关闭 ), 
         * 在进入该方法后, 是否添加了一个线程, 或者线程池是否关闭.
         * 因此, 我们应该再次检查运行状态, 如果需要, 将任务放回队列中, 或者启动一个新线程.
         *
         * 3. 如果不能将任务入队, 尽量添加一个新线程. 
         * 如果添加失败, 此时线程池可能关闭, 或者运行线程数等于最大线程数, 需要拒绝该任务.
         */
        int c = ctl.get();
        // 当前运行的线程数 小于 核心线程数 
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
    // 确保处于运行状态, 然后将任务添加到队列中
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            // 如果不处于运行状态, 从队列中移除
            if (! isRunning(recheck) && remove(command))
                reject(command);  // 拒绝任务
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 尝试添加任务 
        else if (!addWorker(command, false))
            reject(command);
    }
}



目录
相关文章
|
11月前
|
数据采集 监控 API
告别手动埋点!Android 无侵入式数据采集方案深度解析
传统的Android应用监控方案需要开发者在代码中手动添加埋点,不仅侵入性强、工作量大,还难以维护。本文深入探讨了基于字节码插桩技术的无侵入式数据采集方案,通过Gradle插件 + AGP API + ASM的技术组合,实现对应用性能、用户行为、网络请求等全方位监控,真正做到零侵入、易集成、高稳定。
1055 94
|
Java 中间件 调度
【源码】【Java并发】从InheritableThreadLocal和TTL源码的角度来看父子线程传递
本文涉及InheritableThreadLocal和TTL,从源码的角度,分别分析它们是怎么实现父子线程传递的。建议先了解ThreadLocal。
609 4
【源码】【Java并发】从InheritableThreadLocal和TTL源码的角度来看父子线程传递
|
安全 Java Android开发
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
543 0
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
|
安全 Java 调度
Netty源码—3.Reactor线程模型二
本文主要介绍了NioEventLoop的执行总体框架、Reactor线程执行一次事件轮询、Reactor线程处理产生IO事件的Channel、Reactor线程处理任务队列之添加任务、Reactor线程处理任务队列之执行任务、NioEventLoop总结。
|
安全 Java
Netty源码—2.Reactor线程模型一
本文主要介绍了关于NioEventLoop的问题整理、理解Reactor线程模型主要分三部分、NioEventLoop的创建和NioEventLoop的启动。
|
XML JavaScript Android开发
【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式
本文总结了Android中几种常用的网络技术,包括WebView、HttpURLConnection、OKHttp和XML的Pull解析方式。每种技术都有其独特的特点和适用场景。理解并熟练运用这些技术,可以帮助开发者构建高效、可靠的网络应用程序。通过示例代码和详细解释,本文为开发者提供了实用的参考和指导。
667 15
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
10956 2
|
Java 调度
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
675 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
|
消息中间件 JavaScript 前端开发
最细最有条理解析:事件循环(消息循环)是什么?为什么JS需要异步
度一教育的袁进老师谈到他的理解:单线程是异步产生的原因,事件循环是异步的实现方式。 本质是因为渲染进程因为计算机图形学的限制,只能是单线程。所以需要“异步”这个技术思想来解决页面阻塞的问题,而“事件循环”是实现“异步”这个技术思想的最主要的技术手段。 但事件循环并不是全部的技术手段,比如Promise,虽然受事件循环管理,但是如果没有事件循环,单一Promise依然能实现异步不是吗? 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。

推荐镜像

更多