线程性能

线程性能

  熟练使用android线程能帮助你提升应用的性能。此页讨论了采用线程工作的几个方面:采用UI或主线程工作;应用生命周期和线程优先级的关系;和采用平台提供的方法管理复杂的线程。在任何一方面,该页描述了避免这些问题的潜在陷阱和策略。

Main Thread

  但你启动应用程序的时候,Android创建了一个新的Linux进程以及执行线程。这就是main线程,也被称为UI线程,负责屏幕上发生的任何事情。

  理解它是如何工作的能帮助你设计应用程序以使用主线程获得最佳性能。

Internals

  主线程设计非常简单:它唯一的工作是从线程安全的工作队列中获取和执行工作块,直到其应用程序终止。该框架从各种场合生成一些这些工作块。该场合包括与生命周期相关联的回调,用户事件(如输入),或来自其它应用程序和进程的事件。此外,应用程序可以自己明确地排队,而不使用框架的。

  你应用程序执行的几乎所有的代码块都是和事件回调相联系的,例如,输入,layout inflation或绘制。当某些事情触发事件时,事件发生的线程把事件推出其自身之外,并进入主线程的消息队列。然后主线程为事件提供服务。

  当动画或者屏幕更新正在执行,系统尝试在每隔16ms或者更快的时间内执行工作块(负责屏幕绘制),以便以60帧/分钟平滑的呈现。为了使系统达到这个目标、UI/View的层次结构必须在主线程上更新。然而,当主线程消息队列包含太多或者太长任务时,为使主线程足够快的完成更新,应用程序应该把工作移到工作线程。假如主线程不能在16ms内执行完工作块,用户可能观察到hitching,lagging或UI输入的响应不足。假如主线程阻塞大约5秒,系统显示Application Not Responding(ANR)提示框,以允许用户直接关闭应用程序。

  从主线程异常许多或长任务,这样它们就不会干扰平滑渲染和快速响应用户的输入,这就是你在应用程序中采用线程的主要原因。

Threading and UI Object References

  通过设计,Android View对象不是线程安全的。一个应用程序有望在主线程上创建,使用和销毁UI对象。如果您尝试在主线程以外的其它线程中修改甚至引用UI对象,则结果可能是异常,静默失败,崩溃和其他未定义的不当行为。

  引用的问题分为两个不同的类别:显式引用和隐式引用。

Explicit references(显式引用)

  非主线程上的许多任务具有更新UI对象的最终目标。但是,如果这些线程中的一个访问视图层次结构中的对象,则可能导致应用程序不稳定:如果工作线程在任何其他线程引用对象的同时更改该对象的属性,则结果未定义。

  例如,考虑一个在工作线程上直接引用UI对象的应用程序。工作线程上的对象可能包含对View的引用;但在工作完成之前,视图被从视图层次结构中删除。当这两个动作同时发生时,引用将View对象保存在内存中并设置属性。但是,用户永远不会看到此对象,并且一旦对该对象的引用消失,该应用就会删除该对象。

  在另一个示例中,View对象包含对拥有它们的activity的引用。如果该activity被销毁,但仍然有直接或间接引用它的线程工作块,则垃圾收集器将不会收集activity,直到该工作块完成执行。

  在一些activity生命周期事件(如屏幕旋转)发生的情况下,这种情况会导致线程工作可能在运行的情况下出现问题。在运行工作完成之前,系统将无法执行垃圾回收。因此,内存中可能会有两个Activity对象,直到垃圾收集发生。

  使用这些场景,我们建议您的应用程序不包括在线程工作任务中明确引用UI对象。避免这样的引用可以帮助您避免这些类型的内存泄漏,同时也可以避免线程争用。

  在所有情况下,您的应用程序只能更新主线程上的UI对象。这意味着您应该制定一个允许多个线程将工作回传到主线程的协商策略,哪些任务是最新activity或fragment与更新实际UI对象的工作。

Implicit references(隐式引用)

  在下面的代码片段中可以看到具有线程对象的常见代码设计缺陷:

public class MainActivity extends Activity {
  // …...
  public class MyAsyncTask extends AsyncTask<Void, Void, String>   {
    @Override protected String doInBackground(Void... params) {...}
    @Override protected void onPostExecute(String result) {...}
  }
}

  这个代码段的缺点在于,该代码将线程对象MyAsyncTask声明为某些activity的非静态内部类。此声明将为封装的Activity实例创建一个隐式引用。因此,在线程完成之前,该对象包含对该activity的引用,导致引用的activity的回收延迟。反过来,这种延迟给内存带来了更大的压力。

  直接解决这个问题的方法是将重载的类实例定义为静态类,或者在自己的文件中,从而删除隐式引用。

  另一个解决方案是将AsyncTask对象声明为静态嵌套类。这样做消除了隐式引用问题,因为静态嵌套类与内部类不同:内部类的实例需要实例化外部类的实例,并且可以直接访问其封装类实例的方法和字段。相比之下,静态嵌套类不需要引用封装类(enclosing class)的实例,因此它不包含外部类成员的引用。

public class MainActivity extends Activity {
  // …...
  static public class MyAsyncTask extends AsyncTask<Void, Void, String>   {
    @Override protected String doInBackground(Void... params) {...}
    @Override protected void onPostExecute(String result) {...}
  }
}

Threading and App and Activity Lifecycles

  应用程序生命周期可能会影响应用程序中线程的工作原理。您可能需要确定线程在活动被销毁后应该还是不应该持续。您还应该了解线程优先级与活动是否在前台或后台运行之间的关系。

Persisting threads(持续线程)

  线程在产生它们的activities的生命周期中持续存在。线程继续执行,不间断,不管activities的创建或破坏。在某些情况下,这种持久性是可取的。

  考虑一种情况,其中activity产生一组线程工作块,然后在工作线程可以执行块之前被销毁。应用程序应该怎么处理运行中的块?

  如果这些块将要更新不再存在的UI,则无需继续工作。例如,如果工作是从数据库加载用户信息,然后更新视图,则线程不再需要。

  相比之下,工作包可能具有与UI完全不相关的一些好处。在这种情况下,你应该持续线程。例如,数据包可能正在等待下载图像,将其缓存到磁盘,并更新关联的View对象。虽然该对象不再存在,如果用户返回被销毁的activity,下载和缓存图像的行为可能仍然有帮助。

  对所有线程对象手动管理生命周期响应可能变得非常复杂。如果您不正确管理它们,您的应用程序可能会遇到内存争用和性能问题。Loaders是这个问题的一个解决方案。Loader有助于异步加载数据,同时还可以通过配置更改来持久化信息。有关更多信息,请参阅AsyncTaskLoader

Thread priority

  如过程和应用程序生命周期Processes and the Application Lifecycle)中所述,应用程序的线程接收的优先级部分取决于应用程序在应用程序生命周期中的位置。当您在应用程序中创建和管理线程时,重要的是设置其优先级,以便正确的线程在正确的时间获取正确的优先级。如果设置太高,您的线程可能会中断UI线程和RenderThread,导致您的应用程序丢帧。如果设置太低,您可能使异步任务(如图像加载)比其需要更慢。

  每次创建线程时,都应该调用setThreadPriority()。系统的线程调度程序优先考虑具有高优先级的线程,平衡这些优先级以及最终完成所有工作的需要。通常,前台组中的线程从设备获取约95%的总执行时间,而后台组大约占5%。

  系统还使用Process类为每个线程分配自己的优先级值。

  默认情况下,系统将线程的优先级设置为与生成线程相同的优先级和组成员资格。但是,您的应用程序可以使用setThreadPriority()显式调整线程优先级。

  Process类通过提供一组您的应用程序用于设置线程优先级的常量来帮助降低分配优先级值的复杂性。例如,THREAD_PRIORITY_DEFAULT表示线程的默认值。您的应用程序应将执行较不紧急工作线程的优先级设置为THREAD_PRIORITY_BACKGROUND线程。

  您的应用程序可以使用THREAD_PRIORITY_LESS_FAVORABLE和THREAD_PRIORITY_MORE_FAVORABLE常量作为增量器来设置相对优先级。有关线程优先级的列表,请参阅Process类中的THREAD_PRIORITY常量。

  有关管理线程的更多信息,请参阅有关Thread和Process类的参考文档。

Helper Classes for Threading

  该框架提供了相同的Java类和基类以便于线程化,如Thread,Runnable和Executors类。为了帮助减少与Android开发线程应用程序相关联的认知负载,该框架提供了一组可帮助开发的帮助者,例如AsyncTaskLoaderAsyncTask。每个助手类都有一组特定的性能细微差别,使得它们对于特定的线程问题子集是唯一的。对错误的情况使用错误的类可能会导致性能问题。 

The AsyncTask class

  AsyncTask类是一个简单有用的原始应用程序对于需要将工作从主线程快速移动到工作线程上。例如,输入事件可能会触发需要使用加载的位图来更新UI。AsyncTask对象可以将位图加载和解码卸载到备用线程;一旦处理完成,AsyncTask对象就可以管理接收工作返回到主线程上,以更新UI。

  使用AsyncTask时,请记住几个重要的性能方面。首先,默认情况下,应用程序将其创建的所有AsyncTask对象推送到单个线程中。因此,它们以串行方式执行,并且与主线程一样,特别长的工作包可以阻止队列。因此,我们建议您只使用AsyncTask处理短于5ms的工作项。

  AsyncTask对象也是隐式应用问题的最常见的发生者。AsyncTask对象存在与显式引用相关的风险,但这些对象有时更易于解决。例如,AsyncTask可能需要对UI对象的引用,以便在AsyncTask在主线程上执行其回调时正确更新UI对象。在这种情况下,您可以使用WeakReference存储对所需UI对象的引用,并且一旦AsyncTask在主线程上运行,就可以访问对象。要清楚,对对象持有一个WeakReference不会使对象线程安全; WeakReference仅提供一种处理显示引用和垃圾收集问题的方法。

The HandlerThread class

  虽然AsyncTask是有用的,但它可能并不总是您的线程问题的正确解决方案(it may not always be the right solution)。相反,您可能需要一种更传统的方法来在更长时间运行的线程上执行一个工作块,以及一些手动管理该工作流的功能。

  从Camera对象获取预览框架时,考虑一个常见的挑战。当您注册相机预览框时,您可以在onPreviewFrame()回调中接收它们,该调用在调用的事件线程上被调用。如果在UI线程上调用了这个回调函数,处理巨大像素数组的任务将会干扰渲染和事件处理工作。同样的问题也适用于AsyncTask,它也连续执行作业并容易受到阻塞。

  这是一个处理程序线程是适当的情况:处理程序线程实际上是一个长时间运行的线程,它从队列中抓取工作并对其进行操作。在这个例子中,当你的应用程序将Camera.open()命令委托给处理程序线程上的一个工作块时,相关的onPreviewFrame()回调位于处理程序线程上,而不是UI或AsyncTask线程。所以,如果要在像素上做长时间的工作,这可能是一个更好的解决方案。

  当您的应用程序使用HandlerThread创建一个线程时,不要忘记根据其工作类型设置线程的优先级。记住,CPU只能并行处理少量线程。确定优先级有助于系统知道在所有其他线程正在争取注意时安排此工作的正确方法

The ThreadPoolExecutor class

  有些类型的工作可以减少高度并行的分布式任务。例如,一个这样的任务是计算一个800万像素图像的每个8×8块的滤镜。使用这个创建的工作包的绝对数量,AsyncTask和HandlerThread不是适当的类。AsyncTask的单线程性质将所有线程化工作转换为线性系统。

  ThreadPoolExecutor是一个帮助类,使此过程更容易。该类管理一组线程的创建,设置其优先级,并管理如何在这些线程之间分配工作。随着工作负载的增加或减少,该类会自动启动或销毁更多线程以适应工作负载。

  这个类也可以帮助你的应用程序产生最佳线程数。当它构造一个ThreadPoolExecutor对象时,应用程序设置最小和最大线程数。当给予ThreadPoolExecutor的工作量增加时,类将考虑初始化的最小和最大线程数,并考虑待处理的工作量。基于这些因素,ThreadPoolExecutor决定在任何给定时间应该有多少个线程。

How many threads should you create?

  尽管从软件层面来看,您的代码可以创建数百个线程,但这样做可能会导致性能问题。您的应用程序与后台服务,渲染器,音频引擎,网络等共享有限的CPU资源。CPU真的只有并行处理少量线程的能力;上面的一切都处于优先级和调度问题。因此,只需要创建与工作负载需求一样多的线程就很重要。

  实际上,有一些变数负责这个,但挑选一个价值(如4,为初学者),并用Systrace测试它是与其他任何策略一样坚实。您可以使用试错来发现可以使用的最少线程数,而不会遇到问题。

  决定有多少线程的另一个考虑是线程不是免费的:它们占用内存。每个线程至少需要64k的内存。这在安装在设备上的许多应用程序中迅速增加,特别是在调用堆栈显着增长的情况下。

  许多系统进程和第三方库通常会旋转自己的线程池。如果您的应用程序可以重用现有的线程池,则此重用可能会通过减少内存和处理资源的争用来帮助执行性能。

原文地址:https://developer.android.google.cn/topic/performance/threads.html

时间: 2024-10-29 02:23:28

线程性能的相关文章

[转载]Linux 线程实现机制分析

本文转自http://www.ibm.com/developerworks/cn/linux/kernel/l-thread/ 支持原创.尊重原创,分享知识! 自从多线程编程的概念出现在 Linux 中以来,Linux 多线应用的发展总是与两个问题脱不开干系:兼容性.效率.本文从线程模型入手,通过分析目前 Linux 平台上最流行的 LinuxThreads 线程库的实现及其不足,描述了 Linux 社区是如何看待和解决兼容性和效率这两个问题的. 一.基础知识:线程和进程 按照教科书上的定义,进

线程同步-iOS多线程编程指南(四)-08-多线程

首页 编程指南 Grand Central Dispatch 基本概念 多核心的性能 Dispatch Sources 完结 外传:dispatch_once(上) Block非官方编程指南 基础 内存管理 揭开神秘面纱(上) 揭开神秘面纱(下) iOS多线程编程指南 关于多线程编程 线程管理 Run Loop 线程同步 附录 Core Animation编程指南 Core Animation简介 基本概念 渲染架构 几何变换 查看目录 中文手册/API ASIHTTPRequest Openg

Sysbench0.5测试数据库性能

一.sysbench介绍SysBench是一个模块化的.跨平台.多线程基准测试工具,主要用于评估测试各种不同系统参数下的数据库负载情况.它主要包括以下几种方式的测试:1.cpu性能2.磁盘io性能3.调度程序性能4.内存分配及传输速度5.POSIX线程性能6.数据库性能(OLTP基准测试 目前sysbench主要支持MySQL,pgsql,Oracle等数据库.最早的官网 http://sysbench.sourceforge.net      已经不可用项目地址: https://launch

编程思想之多线程与多进程(1)——以操作系统的角度述说线程与进程

什么是线程 什么是线程?线程与进程与有什么关系?这是一个非常抽象的问题,也是一个特别广的话题,涉及到非常多的知识.我不能确保能把它讲的话,也不能确保讲的内容全部都正确.即使这样,我也希望尽可能地把他讲通俗一点,讲的明白一点,因为这是个一直困扰我很久的,扑朔迷离的知识领域,希望通过我的理解揭开它一层一层神秘的面纱. 任务调度 线程是什么?要理解这个概念,须要先了解一下操作系统的一些相关概念.大部分操作系统(如Windows.Linux)的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执

Netty的线程模型

当我们讨论Netty线程模型的时候,一般首先会想到的是经典的Reactor线程模型,尽管不同的NIO框架对于Reactor模式的实现存在差异,但本质上还是遵循了Reactor的基础线程模型.下面浅谈一下我对Reactor线程模型的认识 1.Reactor单线程模型,是指所有的I/O操作都在同一个NIO线程上面完成.NIO线程的职责如下 作为NIO服务端,接收客户端的TCP连接 作为NIO客户端,向服务端发起TCP连接 读取通信对端的请求或者应答消息 向通信对端发送消息请求或者应答消息 对于小容量

Java application 性能分析分享

性能分析的主要方式 监视:监视是一种用来查看应用程序运行时行为的一般方法.通常会有多个视图(View)分别实时地显示 CPU 使用情况.内存使用情况.线程状态以及其他一些有用的信息,以便用户能很快地发现问题的关键所在. 转储:性能分析工具从内存中获得当前状态数据并存储到文件用于静态的性能分析.Java 程序是通过在启动 Java 程序时添加适当的条件参数来触发转储操作的.它包括以下三种: 系统转储:JVM 生成的本地系统的转储,又称作核心转储.一般的,系统转储数据量大,需要平台相关的工具去分析,

【转】以操作系统的角度述说线程与进程

转自:http://blog.csdn.net/luoweifu/article/details/46595285 什么是线程 什么是线程?线程与进程与有什么关系?这是一个非常抽象的问题,也是一个特别广的话题,涉及到非常多的知识.我不能确保能把它讲的话,也不能确保讲的内容全部都正确.即使这样,我也希望尽可能地把他讲通俗一点,讲的明白一点,因为这是个一直困扰我很久的,扑朔迷离的知识领域,希望通过我的理解揭开它一层一层神秘的面纱. 任务调度 线程是什么?要理解这个概念,须要先了解一下操作系统的一些相

进程与线程的区别,以及多进程与多线程

什么是线程 什么是线程?线程与进程与有什么关系?这是一个非常抽象的问题,也是一个特别广的话题,涉及到非常多的知识.我不能确保能把它讲的话,也不能确保讲的内容全部都正确.即使这样,我也希望尽可能地把他讲通俗一点,讲的明白一点,因为这是个一直困扰我很久的,扑朔迷离的知识领域,希望通过我的理解揭开它一层一层神秘的面纱. 任务调度 线程是什么?要理解这个概念,须要先了解一下操作系统的一些相关概念.大部分操作系统(如Windows.Linux)的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执

.NET面试题解析(07)-多线程编程与线程同步

系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 关于线程的知识点其实是很多的,比如多线程编程.线程上下文.异步编程.线程同步构造.GUI的跨线程访问等等,本文只是从常见面试题的角度(也是开发过程中常用)去深入浅出线程相关的知识.如果想要系统的学习多线程,没有捷径的,也不要偷懒,还是去看专业书籍的比较好. 常见面试题目: 1. 描述线程与进程的区别? 2. 为什么GUI不支持跨线程访问控件?一般如何解决这个问题? 3. 简述后台线程和前台线程的区别? 4. 说说常