Android 线程与线程池

news2024/11/4 21:40:44

概述

首先来简单了解一下线程,在Android中线程分为UI线程与子线程,所谓UI线程也就是主线程,UI线程主要用于处理界面相关的事,子线程则是用于执行耗时任务。

在Android中,扮演线程角色的除Thread外,还有AsyncTask、IntentService、HandlerThread。这几种不同的线程形式拥有不同的特性和使用场景,下面一一介绍一下:

  • AsyncTask封装了线程池和Handler,它的主要作用是为了方便开发者在子线程通知主线程更新UI
  • HandlerThread是一种具有消息循环的线程,在它的内部可以使用Handler
  • IntentService是一个服务,系统对其进行了封装使其可以方便执行后台任务,IntentService内部采用HandlerThread来执行任务,当任务执行完毕后IntentService会自动退出

当我们在一个进程中频繁创建和销毁线程,这会带来巨大的系统开销,显然不是一个高效的做法,正确的做法是采用线程池,一个线程池中会缓存一定数量的线程,这样可以节省大量开销。

三种线程形式

AsyncTask

AsyncTask是一种轻量级异步任务类,它可以在线程池中执行后台任务,然后将执行的进度和最终结果传递给主线程并在主线程中更新UI。从实现上来说,AsyncTask封装了Thread和Handler,通过AsyncTask可以更加方便执行后台任务以及在主线程中访问UI,但AsyncTask并不适合进行特别耗时的后台任务,对于特别耗时的后台任务来说民间一使用线程池。

AsyncTask是一个抽象类,我们要使用它得创建一个子类去继承它,在继承AsyncTask我们可以为AsyncTask类指定三个参数:

  • Params:在执行AsyncTask时需要传入的参数,这些参数参数可用于后台任务中使用
  • Progress:后台任务执行时,如果需要在界面上展示当前进度,使用该泛型作为进度单位
  • Result:当任务执行完毕,如果需要对结果进行返回,则使用这里的泛型作为返回值类型

如下简单示例:

class DownLoadTask extends AsyncTask<Void,Integer,Boolean>{}

第一个参数Void表示不需要传递参数给后台任务使用,第二个参数Integer表示使用整数作为进度参数单位,第三个Boolean参数表示使用布尔数据来反馈执行结果

接着还要去重写AsyncTask中四个重要的方法:

  • onPreExecute()

这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作

  • doInBackground(Params…)

这个方法中的所有代码都会在子线程中运行,我们在这里进行耗时任务的处理,可以通过return将执行结果返回,如果AsyncTask类第三个参数是Void,就不需要return了。这个方法中不能进行UI操作,如果需要更新UI元素,调用publishProgress(Progress…)方法完成

  • onProgressUpdate(Progress…)

调用了publishProgress(Progress…)方法之后,onProgressUpdate(Progress…)方法很快就会被调用,在这个参数可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新

  • onPostExecute(Result)

当后台任务执行完并通过return进行结果返回时,这个方法就会很快被调用,返回的数据作为参数传递到此方法中,可以根据这个参数进行一些UI操作

例如我们要实现一个后台下载功能的自定义AsyncTask类DownloadTask:

class DownloadTask extends AsyncTask<Void,Integer,Boolean>{
    @Override
    protected void onPreExecute(){
        progressDialog.show();//显示进度条
    }
    @Override
    protected Boolean doInBackground(Void...params){
        try{
            while(true){
                int downloadPercent=download();//虚构方法,表示进行下载任务
                publishProgress(downloadPercent);
                if(downloadPercent>=100){//进度到达100,结束
                    break;
                }
            }
        }catch(Exception e){
            return false;
        }
        return true;
    }
    @Override
    protected void onProgressUpdate(Integer...values){
        progressDialog.setMessage("下载进度");//更新下载进度
    }
    @Override
    protected void onPostExecute(Boolean result){
        progressDialog.dismiss();//关闭进度对话框
        if(result){
            //提示下载成功
        }else{
            //提示下载失败
        }
    }
}

这样我们就完成了一个具体的AsyncTask任务类,相信也能大概理解使用AsyncTask的诀窍了,就是在donInBackground()方法中执行具体的耗时任务,在onProgressUpdate()方法中进行UI操作,在onPostExecute()方法中执行一些任务的收尾工作。

想使用这个任务类,只需:

new DownloadTask.execute();

AsyncTask在使用过程中还存在一些条件限制,如下:

  • AsyncTask必须在主线程中加载,也就是说第一次访问AsyncTask必须发生在主线程
  • AsyncTask的对象必须在主线程中创建
  • execute方法必须在UI线程调用
  • 不要在程序中直接调用onPreExecute、onPostExecute、donInBackground和onProgessUpdate方法
  • 一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会运行异常

HandlerThread

HandlerThread继承了Handler,它是一种可以使用Handler的Thread,它的实现非常简单,我们可以打开HandlerThread的源码看看,这里我们就看它的run方法:

	@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();//创建Looper对象
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

可以看到这里我们通过Looper.prepare()来创建消息队列,通过Looper.loop()来开启消息循环,这样我们就可以在实际使用时在HandlerThread中使用Handler了。

从上面我们可以看出HandlerThread和Thread的不同之处:

  • Thread主要是在run方法中去执行耗时任务
  • HandlerThread在内部创建了消息队列,外界需要通过Handler的消息方式来通知HandlerThread来执行一个具体的任务

由于HandlerThread的run方法是一个无限循环,因此明确不需要使用HandlerThread时,可以通过它的quit或quitSafely方法来终止线程的执行。

IntentService

上面介绍到了HandlerThread,HandlerThread一个使用场景就是IntentService。IntentService是一种特殊的Service,它继承了Service并且它是一个抽象类,所以必须创建它的子类才可以使用IntentService。

IntentService可用于执行后台的耗时任务,当任务执行后它会自动停止。因为IntentService是服务,所以它的优先级比单纯的线程高很多,所以IntentService比较适合执行一些高优先级的后台任务

在IntentService的实现上,它封装了HandlerThread和Handler,我们可以看以下IntentService的onCreate方法:

	@Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");//HandlerThread对象
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);//Handler对象
    }

可以看到在onCreate方法中会创建一个HandlerThread,上面说到在HandlerThread中会创建出一个Looper对象,所以在这我们就可以获取这个Looper对象并使用这个Looper对象创建出一个Handler对象mServiceHandler,这样我们通过mServiceHandler发送的消息都会在HandlerThread中执行

在每次启动IntentService时,它的onStartCommand方法就会调用一次,IntentService在onStartCommand方法中去处理每个后台任务的Intent,我们可以看看onStartCommand方法的源码:

	@Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

在onStartCommand方法中调用了onStart方法,打开onStart方法看看:

	@Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

可以看到这里仅是通过mServiceHandler发送了一条消息,这个消息会在HandlerThread中被处理。mServiceHandler在接收到消息后,会将这个消息交给onHandleIntent方法处理,我们可以看看mServiceHandler对象的类ServiceHandler的实现:

	private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

可以看到在onHandleIntent方法结束后会调用stopSelf(int startId)方法而不是stopSelf()方法来停止服务,这是因为stopSelf(int startId)方法会等待所有消息都处理完后再停止服务

这里的onHandleIntent()方法是一个抽象方法,我们需要在子类中实现。另外我们需要知道的是,Handler中的Looper是顺序执行后台任务的,当多个后台任务存在时,这些后台任务也是按照外界发起的顺序排队执行的。

线程池

Android中的线程池的概念来源于Java中的Executor,Executor是一个接口,真正的线程池实现为ThreadPoolExecutor。THreadPoolExecutor提供了一系列参数来配置线程池,通过不同的参数可以创建不同的线程池,从线程池的功能特性来说,可以将线程池分为四类,这四类线程池可以通过Executors所提供的工厂方法来得到(后面再详细介绍)

再介绍线程池一下的好处:

  • 线程池中的线程可以重用,避免因为线程的频繁创建与销毁带来的性能开销
  • 能有效控制线程池的最大并发数,避免大量的线程之间因为互相抢占系统资源而导致的阻塞现象
  • 能够对线程进行简单管理,并提供定时执行以及指定间隔循环执行等功能

ThreadPoolExecutor

ThreadPoolExecutor是线程池的真正实现,它的构造方法提供了一系列的参数来配置线程池,这些参数直接影响线程池的功能特性,如下是ThreadPoolExecutor一个常用的构造方法:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory)
  • corePoolSize:线程池的核心线程数。默认情况下核心线程会一直存货,即使它们处于闲置状态
  • maximumPoolSize:线程池能容纳的最大线程数,当活动线程到达这个数值后,后续的新任务会被阻塞
  • keepAliveTime:非核心线程闲置时的超时时长,超过这个时长,非核心线程会被回收
  • unit:指定keepAliveTime参数的时间单位 TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分钟)
  • workQueue:线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会被存储在这个参数中
  • threadFactory:线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,它只有一个方法:Thread newThread(Runnable r)

ThreadPoolExecutor执行任务时大致遵顼如下规则:

  • 如果线程池中的线程数量为达到核心线程数,那么就直接启动一个核心线程来执行任务
  • 如果线程池中的线程数量已达到或超过核心线程的数量,那么任务就会被插入到任务队列中排队等待执行
  • 如果上面一条中无法将任务插入任务队列中了,这往往是由于任务队列已满,这时如果线程数量未达到线程池规定的最大值,那么就会立即启动一个非核心线程来执行任务
  • 如果上面一条线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务

四类线程池

根据不同的功能特性,可以将线程池分为四类常见的线程池:

  • FixedThreadPool

通过Executors的newFixedThreadPool方法来创建。它是一种线程数量固定的线程池,当线程处于空闲时,它们并不会被回收,除非线程池被关闭了。当所有的线程都处于活动状态时,新任务都会处于等待状态,直到线程空闲出来。

由于FixedThreadPool只有核心线程并且这些核心线程不会被回收,这意味着它能更加快速响应外界的请求。

  • CachedThreadPool

通过Executor的newCacheThreadPool方法来创建。它是一种线程数量不定的线程池,它只有非核心线程,并且最大线程数为Integer.MAX_VALUE,实际上就相当于线程数量可以任意大。当线程池中的线程都处于活动状态时,线程池会创建新的线程来处理新任务,否则就会利用闲置的线程来处理。

CacheThreadPool适合执行大量的耗时较少的任务。

  • ScheduledThreadPool

通过Executors的newScheduledThreadPool方法来创建。它的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置时会被立即回收。

ScheduledThreadPool适用于执行定时任务和具有固定周期的重复任务。

  • SingleThreadExecutor

通过Executors的newSingleThreadExecutor方法来创建。这类线程池内部只有一个核心线程,它确保所有任务都在同一个线程中按顺序执行。

SingleThreadExecutor的意义在于统一所有的外界任务到一个线程中执行,这使得这些任务之间不需要处理线程同步的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2232101.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

时序数据库是什么:概念、特点与分类简析

时序数据与时序数据库的“保姆级”科普&#xff01; 作为将数据价值转化为产能能效的“核心大脑”&#xff0c;数据库的发展依然处于加速期&#xff0c;面向不同数据类型的数据库类型也在不断增加。 在众多细分领域数据库类型中&#xff0c;伴随制造业数字化转型的行业趋势和多…

【创建型】单例模式

单例模式使用的场景&#xff1a;需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即&#xff1a;重量级对象)&#xff0c;但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等) 1. 饿汉式&#xff08;静态常量&#xf…

6.0、静态路由

路由器最主要的功能就是转发数据包。路由器转发数据包时需要查找路由表&#xff08;你可以理解为地图&#xff09;&#xff0c;管理员可以直接手动配置路由表&#xff0c;这就是静态路由。 1.什么是路由&#xff1f; 在网络世界中&#xff0c;路由是指数据包在网络中的传输路…

工业通信网关的各项功能解析-天拓四方

在工业自动化和智能制造的浪潮中&#xff0c;工业通信网关作为连接工业现场与互联网的重要桥梁&#xff0c;发挥着至关重要的作用。它不仅实现了不同网络协议之间的转换&#xff0c;还在数据采集、设备控制、网络管理等方面展现出强大的功能。 一、协议转换功能 工业通信网关…

数据结构与算法——Java实现 53.力扣938题——二叉搜索树的范围和

生命的意义 在于活出自我 而不是成为别人眼中的你 —— 24.11.3 938. 二叉搜索树的范围和 给定二叉搜索树的根结点 root&#xff0c;返回值位于范围 [low, high] 之间的所有结点的值的和。 示例 1&#xff1a; 输入&#xff1a;root [10,5,15,3,7,null,18], low 7, high 15 …

TensorRT-LLM的k8s弹性伸缩部署方案

Scaling LLMs with NVIDIA Triton and NVIDIA TensorRT-LLM Using Kubernetes | NVIDIA Technical Blog 一共涉及4个k8s组件&#xff1a; 1. Deployment&#xff1a;跑起来N个pod&#xff1b;指定NVIDIA官方的triton&trt-llm的docker image&#xff0c;指定好model放在哪个…

高亮无惧烈日,强力巨彩租赁屏点亮户外“视”界

在户外显示领域&#xff0c;一款性能出色、适应性强、维护便捷的租赁屏无疑是众多主办方和广告商的首选。强力巨彩旗下的幻云系列租赁屏具备画面清晰、无水波纹、性能稳定、高亮度等诸多优势&#xff0c;可应用于各大户外显示场所&#xff0c;是户外租赁屏市场的明星产品。   …

批量删除redis数据【亲测可用】

文章目录 引言I redis客户端基础操作key的命名规则批量查询keyII 批量删除key使用连接工具进行分组shell脚本示例其他方法III 知识扩展:控制短信验证码获取频率引言 批量删除redis数据的应用: 例如缓存数据使用了新的key存储,需要删除废弃的key。RedisTemplate的key序列化采…

Mysql开发规范

开发规范 对象命名 命名规范的对象&#xff0c;是指数据库SCHEMA、表TABLE、字段COLUMN、索引INDEX、约束CONSTRAINTS等 【强制】凡是需要命名的对象&#xff0c;其标识符不能超过30个字符【强制】名称必须以英文字母开头&#xff0c;不得以 _(下划线) 作为起始和终止字母【…

Web应用性能测试工具 - httpstat

在数字化时代&#xff0c;网站的性能直接影响用户体验和业务成功。你是否曾经在浏览网页时&#xff0c;遇到加载缓慢的困扰&#xff1f;在这个快速变化的互联网环境中&#xff0c;如何快速诊断和优化Web应用的性能呢&#xff1f;今天&#xff0c;我们将探讨一个强大的工具——h…

(57)MATLAB使用迫零均衡器和MMSE均衡器的BPSK调制系统仿真

文章目录 前言一、仿真测试模型二、仿真代码三、仿真结果四、迫零均衡器和MMSE均衡器的实现1.均衡器的MATLAB实现2.均衡器的性能测试 总结 前言 本文给出仿真模型与MATLAB代码&#xff0c;分别使用具有ISI的三个不同传输特性的信道&#xff0c;仿真测试了使用迫零均衡器和MMSE…

用ChatGPT提升工作效率:从理论到实际应用

伴人工智能技术的迅速演进&#xff0c;像ChatGPT这类语言模型已成为提升工作效率的关键工具。这类模型不仅具备处理海量数据的能力&#xff0c;还能自动化许多日常任务&#xff0c;从而提高决策的准确性。本文将深入探讨如何在工作中利用ChatGPT等AI工具提升效率&#xff0c;涵…

MySQL FIND_IN_SET 函数详解

文章目录 1. 基本语法2. 使用场景3. 实战示例3.1 基础查询示例3.2 与其他函数结合使用3.3 动态条件查询 4. 性能考虑5. 常见问题和解决方案5.1 大小写敏感问题5.2 空值处理5.3 模糊匹配 6. 总结 1. 基本语法 FIND_IN_SET 函数的基本语法如下&#xff1a; FIND_IN_SET(str, st…

「Mac畅玩鸿蒙与硬件15」鸿蒙UI组件篇5 - Slider 和 Progress 组件

Slider 和 Progress 是鸿蒙系统中的常用 UI 组件。Slider 控制数值输入&#xff0c;如音量调节&#xff1b;Progress 显示任务的完成状态&#xff0c;如下载进度。本文通过代码示例展示如何使用这些组件&#xff0c;并涵盖 进度条类型介绍、节流优化、状态同步 和 定时器动态更…

ZDH权限-扩展支持数据权限

目录 项目源码 预览地址 安装包下载地址 ZDH权限模块 ZDH权限扩展更细粒度方案 第一种方案&#xff1a; 第二种方案&#xff1a; ZDH权限扩展支持数据权限-新增属性 总结 感谢支持 项目源码 zdh_web: GitHub - zhaoyachao/zdh_web: 大数据采集,抽取平台 预览地址 后…

私有化视频平台EasyCVR海康大华宇视视频平台视频诊断技术是如何实时监测视频质量的?

在现代视频监控系统中&#xff0c;确保视频流的质量和稳定性至关重要。随着技术的进步&#xff0c;视频诊断技术已经成为实时监测视频质量的关键工具。这种技术通过智能分析算法对视频流进行实时评估和处理&#xff0c;能够自动识别视频中的各种质量问题&#xff0c;并给出相应…

Java 用户随机选择导入ZIP文件,解压内部word模板并入库,Windows/可视化Linux系统某麒麟国防系统...均可适配

1.效果 压缩包内部文件 2.依赖 <!--支持Zip--><dependency><groupId>net.lingala.zip4j</groupId><artifactId>zip4j</artifactId><version>2.11.5</version></dependency>总之是要File类变MultipartFile类型的 好像是…

论文笔记(五十四)pi0: A Vision-Language-Action Flow Model for General Robot Control

π0: A Vision-Language-Action Flow Model for General Robot Control 文章概括摘要I. INTRODUCTIONII. RELATED WORKIII. OVERVIEWIV. π 0 \pi_0 π0​模型V. 数据收集和培训配方A. 预训练和后训练B. 语言和高级策略C. 机器人系统细节 VI. 实验评估A. 基础模型评估B. 遵循语…

《AI产品经理手册》——解锁AI时代的商业密钥

在当今这个日新月异的AI时代&#xff0c;每一位产品经理都面临着前所未有的挑战与机遇&#xff0c;唯有紧跟时代潮流&#xff0c;深入掌握AI技术的精髓&#xff0c;才能在激烈的市场竞争中独占鳌头。《AI产品经理手册》正是这样一部为AI产品经理量身定制的实战宝典&#xff0c;…

论文略读:Self-Knowledge Guided Retrieval Augmentation for Large Language Models

2023 emnlp findings RAG 召回的辅助信息不总是有用&#xff0c;甚至可能起负作用 原本对“德牧能不能进机场”这样的问题&#xff0c;ChatGPT是高度认可德牧作为导盲犬的但是检索模块召回了一段“老德牧是一类 balabala 某种狗的争议性名称”的百科介绍作为额外上文输入后&am…