Android 多线程并发优化实现

7e20fa14efab0b0bf433e0bad4c24df5.gif

和你一起终身学习,这里是程序员Android

经典好文推荐,通过阅读本文,您将收获以下知识点:

一、Thread 使用
二、Android Thread
三.线程优先级

一、Thread 使用

在讲解多线程之前,我们先来讲解Thread使用几个需要注意的点:

1.Thread 中断

常用的有两种方式:

(1).通过抛出InterruptedException来中断线程
public  static  class  MyThread extends Thread{
        private  int count=0;
        @Override
        public void run() {
            super.run();
            try{
                while(true){
                        count++;
                        System.out.println("count value:"+count);
                        if (this.interrupted() || this.isInterrupted()){
                            System.out.println("check interrupted show!");
                            throw new InterruptedException();
                        }
                }
            }catch ( InterruptedException e) {
                System.out.println("thread is stop!");
                e.printStackTrace();
            }
        }

    }
(2).通过变量来中断(常用)
public  static  class  CustomThread extends Thread{
        private  int count=0;
        private boolean isCancel = false;
        @Override
        public void run() {
            super.run();
            while(!isCancel){
                    count++;
                    System.out.println("count value:"+count);
            }
        }

        public synchronized void cancel(){
            isCancel = true;
        }
    }

2.Thread 同步

我们分变量同步和代码块同步两个方面来讲解

(1).变量同步
  • 使用volatile关键字

/**
     * 主内存和线程内存缓存进行同步
     */
    volatile int val = 5;
    public int getVal() {
        return val;
    }
    public void setVal(int val) {
        this.val = val;
    }
  • 使用synchronized关键字

int val2 = 5;
    /**
     * 使用一个motinor来监听(实现资源由一个线程进行操作)
     * 主内存和线程内存缓存进行同步
     * @return
     */
    public synchronized int getVal2() {
        return val2;
    }
    public synchronized int setVal2(int val) {
        this.val2 = val;
    }
  • 使用关键字AtomicXXXXX

AtomicInteger mAtomicValue = new  AtomicInteger(0);
    public void setAtomicValue(int value){
        mAtomicValue.getAndSet(value);
    }
    public int getAtomicValue(){
        return mAtomicValue.get();
    }
(2).代码块同步

代码块同步分乐观锁和悲观锁来讲解

使用悲观锁时,其他线程等待,进入睡眠,频繁切换任务,消耗cpu资源

synchronized (this) {
        .....   
    }
使用乐观锁时,失败重试,避免任务重复切换,减少cpu消耗
ReentrantLock lock = new  ReentrantLock();
    lock.lock();
    ......
    lock.unlock();

Thread注意点就讲到这里,下面让我们进入今天的主题,多线程并发优化。

二、Android Thread

android中很多操作需要在主线程中执行,比如UI的操作,点击事件等等,但是如果主线程操作太多,占有的执行时间过长就会出现前面我们说的卡顿现象:

9fd11e7eb4aa6523247db915a301b2b9.jpeg

程序员Android转于网络

为了减轻主线程操作过多,避免出现卡顿的现象,我们把一些操作复杂的消耗时间长的任务放到线程池中去执行。下面我们就来介绍android中几种线程的类。

1.AsyncTask

为UI线程与工作线程之间进行快速的切换提供一种简单便捷的机制。适用于当下立即需要启动,但是异步执行的生命周期短暂的使用场景。
它提供了一种简便的异步处理机制,但是它又同时引入了一些令人厌恶的麻烦。一旦对AsyncTask使用不当,很可能对程序的性能带来负面影响,同时还可能导致内存泄露。(关于内存泄漏在上面已经讲过)

  • 使用AsyncTask需要注意的问题?

(1).在AsyncTask中所有的任务都是被线性调度执行的,他们处在同一个任务队列当中,按顺序逐个执行。一旦有任务执行时间过长,队列中其他任务就会阻塞。

d25ba9babc0cc6b653bb37baab35cc2c.jpeg

程序员Android转于网络

对于上面的问题,我们可以使用AsyncTask.executeOnExecutor()让AsyncTask变成并发调度。

(2).AsyncTask对正在执行的任务不具备取消的功能,所以我们要在任务代码中添加取消的逻辑(和上面Thread类似)
(3).AsyncTask使用不当会导致内存泄漏(可以参考内存泄漏一章)

2.HandlerThread

为某些回调方法或者等待某些任务的执行设置一个专属的线程,并提供线程任务的调度机制。
先来了解下Looper,Handler,MessageQueue
Looper: 能够确保线程持续存活并且可以不断的从任务队列中获取任务并进行执行。
Handler: 能够帮助实现队列任务的管理,不仅仅能够把任务插入到队列的头部,尾部,还可以按照一定的时间延迟来确保任务从队列中能够来得及被取消掉。
MessageQueue: 使用Intent,Message,Runnable作为任务的载体在不同的线程之间进行传递。
把上面三个组件打包到一起进行协作,这就是HandlerThread

304051131cab4c81a27dcc4ddeb13519.jpeg

程序员Android转于网络

我们先来看下源码:

public class HandlerThread extends Thread {
        public HandlerThread(String name, int priority) {
            super(name);
            mPriority = priority;
        }

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

        public Looper getLooper() {
            if (!isAlive()) {
                return null;
            }
            // If the thread has been started, wait until the looper has been created.
            synchronized (this) {
                while (isAlive() && mLooper == null) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                    }
                }
            }
            return mLooper;
        }
    }

从上面的源码发现,HandlerThread其实就是在线程中维持一个消息循环队列。下面我们看下使用:

HandlerThread mHanderThread = new HandlerThread("hanlderThreadTest", Process.THREAD_PRIORITY_BACKGROUND);
    mHanderThread.run();
    Looper mHanderThreadLooper = mHanderThread.getLooper();

    Handler mHandler = new Handler(mHanderThreadLooper){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //子线程中执行
            ...
        }
    };
    //发送消息
    mHandler.post(new Runnable() {
        @Override
        public void run() {
            ...
        }
    });

3.IntentService

适合于执行由UI触发的后台Service任务,并可以把后台任务执行的情况通过一定的机制反馈给UI。
默认的Service是执行在主线程的,可是通常情况下,这很容易影响到程序的绘制性能(抢占了主线程的资源)。除了前面介绍过的AsyncTask与HandlerThread,我们还可以选择使用IntentService来实现异步操作。IntentService继承自普通Service同时又在内部创建了一个HandlerThread,在onHandlerIntent()的回调里面处理扔到IntentService的任务。所以IntentService就不仅仅具备了异步线程的特性,还同时保留了Service不受主页面生命周期影响的特点。

a67f2a6440ba11c08c22f3035d705740.jpeg

程序员Android转于网络

使用IntentService需要特别注意的点:

(1).因为IntentService内置的是HandlerThread作为异步线程,所以每一个交给IntentService的任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。

(2).通常使用到IntentService的时候,我们会结合使用BroadcastReceiver把工作线程的任务执行结果返回给主UI线程。使用广播容易引起性能问题,我们可以使用LocalBroadcastManager来发送只在程序内部传递的广播,从而提升广播的性能。我们也可以使用runOnUiThread()快速回调到主UI线程。

(3).包含正在运行的IntentService的程序相比起纯粹的后台程序更不容易被系统杀死,该程序的优先级是介于前台程序与纯后台程序之间的。

4.Loader

对于3.0后ContentProvider中的耗时操作,推荐使用Loader异步加载数据机制。相对其他加载机制,Loader有那些优点呢?

  • 提供异步加载数据机制

  • 对数据源变化进行监听,实时更新数据

  • 在Activity配置发生变化(如横竖屏切换)时不用重复加载数据

  • 适用于任何Activity和Fragment

下面我们来看下Loader的具体使用:
我们以获得手机中所有的图片为例:

getLoaderManager().initLoader(LOADER_TYPE, null, mLoaderCallback);
    LoaderManager.LoaderCallbacks<Cursor mLoaderCallback = new LoaderManager.LoaderCallbacks<Cursor() {
        private final String[] IMAGE_COLUMNS={
                MediaStore.Images.Media.DATA,//图片路径
                MediaStore.Images.Media.DISPLAY_NAME,//显示的名字
                MediaStore.Images.Media.DATE_ADDED,//添加时间
                MediaStore.Images.Media.MIME_TYPE,//图片扩展类型
                MediaStore.Images.Media.SIZE,//图片大小
                MediaStore.Images.Media._ID,//图片id
        };

        @Override
        public Loader<Cursor onCreateLoader(int id, Bundle args) {
            toggleShowLoading(true,getString(R.string.common_loading));

            CursorLoader cursorLoader = new CursorLoader(ImageSelectActivity.this,                 MediaStore.Images.Media.EXTERNAL_CONTENT_URI,IMAGE_COLUMNS,
                    IMAGE_COLUMNS[4] + "  0 AND "+IMAGE_COLUMNS[3] + " =? OR " +IMAGE_COLUMNS[3] + " =? ",
                    new String[]{"image/jpeg","image/png"},IMAGE_COLUMNS[2] + " DESC");
            return cursorLoader;
        }

        @Override
        public void onLoadFinished(Loader<Cursor loader, Cursor data) {
            if(data != null && data.getCount()  0){
                ArrayList<String imageList = new ArrayList<();

                if(mShowCamera){
                    imageList.add("");
                }
                while (data.moveToNext()){
                    String path = data.getString(data.getColumnIndexOrThrow(IMAGE_COLUMNS[0]));
                    imageList.add(path);
                    Log.e("ImageSelect", "IIIIIIIIIIIIIIIIIIII====="+path);
                }
                //显示数据
                showListData(imageList);
                toggleShowLoading(false,getString(R.string.common_loading));
            }
        }

        @Override
        public void onLoaderReset(Loader<Cursor loader) {  
        }

onCreateLoader() 实例化并返回一个新创建给定ID的Loader对象
onLoadFinished() 当创建好的Loader完成了数据的load之后回调此方法
onLoaderReset() 当创建好的Loader被reset时调用此方法,这样保证它的数据无效
LoaderManager会对查询的操作进行缓存,只要对应Cursor上的数据源没有发生变化,在配置信息发生改变的时候(例如屏幕的旋转),Loader可以直接把缓存的数据回调到onLoadFinished(),从而避免重新查询数据。另外系统会在Loader不再需要使用到的时候(例如使用Back按钮退出当前页面)回调onLoaderReset()方法,我们可以在这里做数据的清除等等操作。

5.ThreadPool

把任务分解成不同的单元,分发到各个不同的线程上,进行同时并发处理。
线程池适合用在把任务进行分解,并发进行执行的场景。
系统提供ThreadPoolExecutor帮助类来帮助我们简化实现线程池。

98c15230f2ac1e8ec8490d20d65bc765.jpeg

程序员Android转于网络

使用线程池需要特别注意同时并发线程数量的控制,理论上来说,我们可以设置任意你想要的并发数量,但是这样做非常的不好。因为CPU只能同时执行固定数量的线程数,一旦同时并发的线程数量超过CPU能够同时执行的阈值,CPU就需要花费精力来判断到底哪些线程的优先级比较高,需要在不同的线程之间进行调度切换。
一旦同时并发的线程数量达到一定的量级,这个时候CPU在不同线程之间进行调度的时间就可能过长,反而导致性能严重下降。另外需要关注的一点是,每开一个新的线程,都会耗费至少64K+的内存。为了能够方便的对线程数量进行控制,ThreadPoolExecutor为我们提供了初始化的并发线程数量,以及最大的并发数量进行设置。

/**
     * 核心线程数
     * 最大线程数
     * 保活时间
     * 时间单位
     * 任务队列
     * 线程工厂
     */
    threadPoolExecutor = new ThreadPoolExecutor(
            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
            linkedBlockingQueue, sThreadFactory);
    threadPoolExecutor.execute(runnable);
  • 我们知道系统还提供了Executors类中几种线程池,下面我们来看下这些线程池的缺点:

newFixedThreadPool 和 newSingleThreadExecutor:主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM。
newCachedThreadPool 和 newScheduledThreadPool:主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM

  • 我们看到这些线程池但是有缺点的,所以具体使用那种方式实现要根据我们的需求来选择。

如果想要避开上面的问题,可以参考OKHttp中线程池的实现,OKHttp中队线程调度又封装了一层,使用安全且方便,有兴趣的可以去看看源码。

三.线程优先级

Android系统会根据当前运行的可见的程序和不可见的后台程序对线程进行归类,划分为forground的那部分线程会大致占用掉CPU的90%左右的时间片,background的那部分线程就总共只能分享到5%-10%左右的时间片。之所以设计成这样是因为forground的程序本身的优先级就更高,理应得到更多的执行时间。

afb942c092657d4d56d45a366f80ebe1.jpeg

程序员Android转于网络

默认情况下,新创建的线程的优先级默认和创建它的母线程保持一致。如果主UI线程创建出了几十个工作线程,这些工作线程的优先级就默认和主线程保持一致了,为了不让新创建的工作线程和主线程抢占CPU资源,需要把这些线程的优先级进行降低处理,这样才能给帮组CPU识别主次,提高主线程所能得到的系统资源。

在Android系统里面,我们可以通过android.os.Process.setThreadPriority(int)设置线程的优先级,参数范围从-20到24,数值越小优先级越高。Android系统还为我们提供了以下的一些预设值,我们可以通过给不同的工作线程设置不同数值的优先级来达到更细粒度的控制。

e4dac4a70db9fbc849041eef04d5a365.jpeg

程序员Android转于网络

大多数情况下,新创建的线程优先级会被设置为默认的0,主线程设置为0的时候,新创建的线程还可以利用THREAD_PRIORITY_LESS_FAVORABLE或者THREAD_PRIORITY_MORE_FAVORABLE来控制线程的优先级。

参考链接:https://www.jianshu.com/p/2795aef8980d

至此,本篇已结束。转载网络的文章,小编觉得很优秀,欢迎点击阅读原文,支持原创作者,如有侵权,恳请联系小编删除,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!

d01946c05e21655e4df203df37a2060b.jpeg

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unity UGUI的DrawCall优化

Unity UGUI是一种强大的用户界面设计工具&#xff0c;它可以帮助开发者快速创建各种界面元素&#xff0c;从按钮和文本到滑块和面板等。然而&#xff0c;在使用UGUI时&#xff0c;一个常见的性能瓶颈就是DrawCall过多导致的性能下降。在本文中&#xff0c;我们将深入探讨UGUI的…

集成使用 GitHub Copilot 提升 IDEA 开发效率

集成使用 GitHub Copilot 提升 IDEA 开发效率 在现代软件开发中&#xff0c;集成开发环境&#xff08;IDE&#xff09;如IntelliJ IDEA已经成为开发人员不可或缺的工具。它们提供了代码编辑、调试、版本控制等一系列功能&#xff0c;极大地提高了开发效率。而GitHub Copilot作…

C# Winfrom实例:武汉智能安检闸机数据接收和解析

项目介绍&#xff1a;本实例主要是接收安检闸机的数据解析并显示到界面上&#xff0c;只做功能实现&#xff0c;不做界面美化 硬件&#xff1a;闸机一个、网线一根、电脑主机开发环境&#xff1a;vs2017 系统&#xff1a;win10涵盖知识点&#xff1a;tcp通讯、文件写入、多线程…

《软件方法(下)》8.2.5.2 属性是否直接描述类(202402更新)(2)

导致出现违反本要点的错误的原因有&#xff1a; &#xff08;1&#xff09;缺少抽象能力 缺少抽象能力的建模人员经常会把手上素材的信息&#xff0c;一一对应地映射为类和属性&#xff0c;导致本来属于多个类的信息被合并在一个类中。 如图8-63&#xff0c;建模人员对照着一…

ETL、ELT区别以及如何正确运用

一、 浅谈ETL、ELT ETL与ELT的概念 ETL (Extract, Transform, Load) 是一种数据集成过程&#xff0c;通常用于将数据从一个或多个源系统抽取出来&#xff0c;经过清洗、转换等处理后&#xff0c;加载到目标数据存储中。这种方法适用于需要对数据进行加工和整合后再加载到目标…

Django学习笔记-HTML实现MySQL的图片上传

1.django项目编写index.html代码 创建form表单,路由指向upload,请求方式post,enctype设置"multipart/form-data", post请求添加{% csrf_token %},编写两个input,上传和提交 2.添加upload路由 3.views中创建upload 1).获取上传的文件,没有上传则返回"没有指定…

打码半年,开源一款自定义大屏设计软件!

hi&#xff0c;大家好&#xff0c;我是Tduck马马。 最近我们开源了一款大屏软件-TReport&#xff0c;与大家分享。 TReport是一款基于Vue3技术栈的数据可视化系统&#xff0c;支持静态、动态api等数据源&#xff1b;可用于数据可视化分析、报表分析、海报设计使用。 提供自定…

Stable Diffusion 绘画入门教程(webui)-图生图

通过之前的文章相信大家对文生图已经不陌生了&#xff0c;那么图生图是干啥的呢&#xff1f; 简单理解就是根据我们给出的图片做为参考进行生成图片。 一、能干啥 这里举两个例子 1、二次元头像 真人转二次元&#xff0c;或者二次元转真人都行&#xff0c; 下图为真人转二次…

.net6 webapi log4net完整配置使用流程

前置&#xff1a;为项目安装如下两个依赖 1.创建文件夹cfgFile 2.创建log4net.Config <?xml version"1.0" encoding"utf-8" ?> <log4net><appender name"ConsoleAppender" type"log4net.Appender.ConsoleAppender"…

使用备份工具xtrabackup进行差异备份详细讲解

差异备份 基于第一天进行差异备份 删除之前修改的数据备份 [rootservice ~]# rm -rf /data/backup/* [rootservice ~]# ls /data/backup 完整备份 [rootservice ~]# xtrabackup --defaults-file/etc/my.cnf --backup --target-dir/data/backup/base/ -uroot -pWyxbuke00. -H…

Collection集合体系(ArrayList,LinekdList,HashSet,LinkedHashSet,TreeSet,Collections)

目录 一.Collection 二.List集合 三.ArrayList集合 四.LinkedList集合 五.Set集合 六.hashSet集合 七.LinkedHashSet集合 八.TreeSet集合 九.集合工具类Collections 集合体系概述 单列集合&#xff1a;Collection代表单列集合&#xff0c;每个元素&#…

大白话说说Docker容器默认网络模型工作原理

Docker的默认网络模型 —— 桥接模式&#xff08;Bridge&#xff09; 当你不做任何特殊设置时&#xff0c;Docker会使用一种叫做“桥接模式”的网络设置。这就像是给你的容器小房子安装了一个虚拟的桥接网络。这座桥连接着容器和你的电脑&#xff08;宿主机&#xff09;&#…

Jmeter之内置函数__property和__P的区别

1. __property函数 作用 读取 Jmeter 属性 语法格式 ${__property(key,var,default)} 参数讲解 小栗子 ${__property(key)} 读取 key 属性如果找不到 key 属性&#xff0c;则返回 key&#xff08;属性名&#xff09; ${__property(key,,default)} 读取 key 属性如果找不到 k…

Flink Task退出流程与Failover机制

这里写目录标题 1 TaskExecutor端Task退出逻辑2 JobMaster端failover流程2.1 Task Execute State Handle2.2 Job Failover2.2.1 Task Failure Handle2.2.2 Restart Task2.2.3 Cancel Task&#xff1a;2.2.4 Start Task 3 Task失败的自动重启策略 1 TaskExecutor端Task退出逻辑 …

算法项目(2)—— LSTM、RNN、GRU(SE注意力)、卡尔曼轨迹预测

本文包含什么? 项目运行的方式(包教会)项目代码LSTM、RNN、GRU(SE注意力)、卡尔曼四种算法进行轨迹预测.各种效果图运行有问题? csdn上后台随时售后.项目说明 本文实现了三种深度学习算法加传统算法卡尔曼滤波进行轨迹预测, 预测效果图 首先看下不同模型的指标: 模型RM…

MySQL学习Day19——索引的数据结构

一、为什么使用索引: 索引是存储引擎用于快速找到数据记录的一种数据结构&#xff0c;就好比一本教课书的目录部分&#xff0c;通过目录中找到对应文章的页码&#xff0c;便可快速定位到需要的文章。MySQL中也是一样的道理&#xff0c;进行数据査找时&#xff0c;首先查看查询…

相机图像质量研究(26)常见问题总结:CMOS期间对成像的影响--坏点

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

FreeRTOS学习笔记——(FreeRTOS中断管理)

这里写目录标题 一、什么是中断&#xff1f;&#xff08;了解&#xff09;二、中断优先级分组设置&#xff08;熟悉&#xff09;三、中断相关寄存器&#xff08;熟悉&#xff09;四、FreeRTOS中断管理实验&#xff08;掌握&#xff09; 一、什么是中断&#xff1f;&#xff08;…

【Azure 架构师学习笔记】- Azure Databricks (8) --UC架构简介

本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Databricks】系列。 接上文 【Azure 架构师学习笔记】- Azure Databricks (7) --Unity Catalog(UC) 基本概念和组件 前言 UC 简单来说&#xff0c;就是管理两样东西&#xff1a;用户和元存储。 用户管理 所有Databri…

Flink 在蚂蚁实时特征平台的深度应用

摘要&#xff1a;本文整理自蚂蚁集团高级技术专家赵亮星云&#xff0c;在 Flink Forward Asia 2023 AI 特征工程专场的分享。本篇内容主要分为以下四部分&#xff1a; 蚂蚁特征平台特征实时计算特征 Serving特征仿真回溯 一、蚂蚁特征平台 蚂蚁特征平台是一个多计算模式融合的高…