Android的线程和线程池

线程的概念

线程分为主线程和子线程,主线程主要处理和界面相关的事情,而子线程往往用于执行耗时操作。由于Android的特性,耗时操作只能在子线程中去执行,除了Thread本身以外,Android中可以扮演线程角色还有如:AsyncTask和IntentService和HandlerThread。尽管三者的表现形式都有别于传统线程,但本质仍为传统的线程。

对于AsyncTask来说,它的底层用到了线程池,对于IntentService和HandlerThread来说,它们的底层直接使用了线程。

AsyncTask封装了线程池和Handler,主要是为了方便开发者在子线程中更新UI。

HandlerThread是一种具有消息循环的线程,在它的内部可以使用Handler。

IntentService内部采用HandlerThread来执行任务,任务执行完毕后IntentService会自动退出。它是一种服务,不容易被系统杀死,可以尽量保证任务的执行,如果IntentService是一个后台线程,由于此时进程中没有活动的四大组件,那么这个进程优先度极低,容易被系统杀死。

操作系统中,线程是系统调度的最小单元,同时线程又是一种受限的系统资源,即线程不可能无限制产生,并且线程的创建和销毁都会有相应的开销。当系统中存在大量的线程,系统会通过时间片轮转的方式调度每个线程,因此线程不可能做到绝对的并行,除非线程的数量小于等于CPU的核心数。正确的做法是采用线程池来缓存一定数量的线程,通过线程池就可以避免因为频繁创建和销毁线程所带来的的系统开销。Android中的线程池来源与JAVA,主要是通过Executor来派生特定类型的线程池,不同种类的线程池又具有各自的特性。

主线程和子线程

java中默认一个进程只有一个线程,这个线程就是主线程。这个主线程主要处理界面交互逻辑,子线程也叫工作线程,主线程为UI线程,要避免主线程由于被耗时操作阻塞从而出现ANR现象。

AsynckTask

AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行进度和最终结果传递给主线程并在主线程中更新UI。

AsynckTask封装了Thread和Handler,通过AsyncTask可以更方便地执行后台任务以及在主线程中更新UI。但是AsyncTask并不适合进行特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池。

AsyncTask是一个抽象的泛型类,它提供了Params、Progress和Result这三个泛型参数,其中Params表示参数的类型,Progress表示后台任务的执行进度的类型,而Result则表示后台任务的返回结果的类型,如果AsyncTask确实不需要传递具体的参数,那么这三个泛型参数可以用void来代替。

public abstract class AsyncTask<ParamsProgressResult>

AsyncTask的四个核心方法

onPreExecute():在主线程执行,在异步任务执行之前调用此方法,用于准备工作。

doInBackground(Params…params):线程池中执行,用于执行异步任务,params参数表示异步任务的输入参数。在此方法中可以通过publishProgress方法来更新任务的进度,publishProgress方法会调用onProgressUpdate方法。另外此方法需要返回计算结果给onPostExecute方法。

onProgressUpdate(Progress…values),在主线程中执行,当后台任务的执行进度发生改变时此方法会被调用。

onPostExecute(Result result),在主线程中执行,在异步任务执行之后,此方法会被调用。

AsyncTask还提供了onCancelled()方法,同样运行在主线程,当异步任务被取消后,此方法被调用,不调用onPostExecute().

AsyncTask的例子

在这里插入图片描述
在这里插入图片描述
通过 new DownloadFilesTask().execute(url1,url2,url3)执行

AsyncTask使用的具体限制

  1. AsyncTask类必须在主线程中加载。在ActivityThread的main方法中,AsyncTask的init方法会被调用,自动满足此条件。
  2. AsyncTask的对象必须在主线程中创建。
  3. execute方法必须在UI线程调用。
  4. 不要在程序中直接调用onPreExecute()、onPostExecute()、doInBackground()和onProgressUpdate()方法。
  5. 一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行时异常。
  6. android1.6前,AT是串行执行任务,1.6后采用线程池处理并行任务,3.0开始AT又采用一个线程来串行执行任务。但3.0后仍可通过AT的executeOnExecutor来并行地执行任务。

AsyncTask的工作原理

AsyncTask执行execute时,会调用executeOnExecutor方法。
在这里插入图片描述在这里插入图片描述
AsyncTask的execute方法传入了sDefaultExecutor这个串行线程池,一个进程内所有的AsyncTask都在这个线程池中排队执行,在executeOnExecutor方法中先执行onPreExecute方法,然后再执行线程池。
在这里插入图片描述
在这里插入图片描述
系统会把AsyncTask的Params参数封装成FutureTask对象,FutureTask是一个并发类,在这里充当Runnable的作用。接着这个FutureTask会交给SerialExecutor的execute方法去处理,SerialExecutor的execute方法首先会把FutureTask对象插入到任务队列mTasks中,若此时没有正在活动的AsyncTask任务,那么就会调用SerialExecutor的scheduleNexte方法来执行下一个AsyncTask任务。同时当一个AsyncTask任务执行完毕,AsyncTask会继续执行其他任务直到所有任务都被执行为止,AsyncTask默认是串行执行。

AsyncTask中有两个线程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler),其中线程池SerialExecutor用于任务的排队,而线程池THREAD_POL_EXECUTOR用于真正地执行任务,InternalHandler用于将执行环境从线程池切换到主线程。

FutureTask的run方法会调用mWorker的call 方法,因此mWorker的call方法最终会在线程池中执行。

在这里插入图片描述
mWorker的call方法中,将mTaskInvoked设为true,表示当前任务已经被调用过了,然后执行AsyncTask的doInBackground,然后将其返回值传递给postResult方法。
在这里插入图片描述
在这里插入图片描述
postResult中通过sHandler发送消息MESSAGE_POST_RESULT
在这里插入图片描述
sHandler是一个静态的Handler对象,为了能够将执行环境切换到主线程,这就要求sHandler这个对象必须在主线程中创建。

handleMessage对于不同类型的消息执行对应操作。

AsyncTask默认串行execute(),可通过executeOnExecutor()并行执行。

HandlerThread

HandlerThread继承了Thread,是一种可以使用Handler的Thread,就是在run方法中通过Looper.prepare()来创建消息队列,并通过Looper.loop来开启消息循环。外界要通过Handler的消息方式来通知HandlerThread执行一个具体的任务,例如IntentService,由于HandlerThread的run方法是一个无限循环,因此当明确不需要使用HandlerThread时,可通过quit或者quitSafely来终止线程的执行。
在这里插入图片描述

IntentService

IntentService是一种特殊的Service,它继承了Service并且它是一个抽象类,因此创建它的子类才能使用IntentService。IntentService可用于执行后台耗时的任务,当任务执行后它会自动停止,同时由于IntentService是服务的原因,这导致它的优先级比单纯的线程高很多,适用于执行一些高优先级的后台任务。

实现上,IntentService封装了HandlerThread和Handler。
在这里插入图片描述
IntentService第一次启动时,调用onCreate,创建一个HandlerThread,并使用其Looper来构造一个Handler对象mServiceHandler,通过mServiceHandler发送的消息最终都会在HandlerThread中执行,从这个角度看,IntentService也适用于执行后台任务。每次启动IntentService都会调用onStarCommand方法,IntentService在其中处理每个后台任务的Intent,每次都会调用onStart。
在这里插入图片描述
IntentService仅仅是通过mServiceHandler发送了一个消息,这个消息会交给HandlerThread处理。mServiceHandler收到消息后,会将Intent对象传递给onHandIntent方法去处理,通过这个Intent对象就可以解析出外界启动IntentService时所传递的参数,通过这些参数就可以区分具体的后台任务,这样在onHandlerIntent方法中针对不同的后台任务进行处理了。当onHandleIntent执行完毕后,IntentService会通过stopSelf(int startId)方法来尝试停止服务,stopSelf()会直接停止服务,而此时可能还有其他消息还没处理,stopSelf(int startId)会等待所有的消息都处理完毕后才终止服务。一般来说,stopSelf(int startId)在尝试停止服务前会判断最近启动服务次数是否和startId相等,如果相等则停止服务,不相等则不停止服务。
在这里插入图片描述
IntentService的onHandleIntent方法是一个抽象方法,它需要我们在子类中实现,它的作用是从Intent参数中区分具体的任务并执行这些任务。每执行一个后台任务就必须启动一次IntentService,而IntentService内部通过消息的方式向HandlerThread请求执行任务,Handler中的Looper是顺序处理消息,意味着后台任务按照外界发起的顺序排队执行。

例:
在这里插入图片描述

线程池

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

线程池的概念

Android中的线程池的概念来源于Java中的Executor,Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor。ThreadPoolExecutor提供了一系列参数来配置线程池,通过不同的参数可以创建不同的线程池,从线程池的功能特性上来说,Android的线程池分为四类,都是直接或间接通过配置ThreadPoolExecutor来实现。

ThreadPoolExecutor

作为线程池的真正实现,它的构造方法提供了一系列参数来配置线程池

public ThreadPoolExecutor( int corePoolSize, 
                           int maxmumPoolSize, 
                           long keepAliveTime, 
                           TimUnit unit, 
                           BlockingQueue<Runnable> workQueue,
                           ThreadFactory threadFactory )
  • corePoolSize:线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即便他们处于闲置状态。如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来时会有超时策略,这个时间间隔由KeepAliveTime指定,超过KeepAliveTime指定时长后,核心线程就会被终止。
  • maximumPoolSize:线程池所嗯呢该容纳的驻点线程数,当活动线程数达到这个数值后,后续的新任务将会被阻塞。
  • keepAliveTime:非核心线程闲置的超时时长,超过这个时长,非核心线程就会被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,keepAliveTime同样会作用于核心线程。
  • unit:用于指定keepAliveTime参数的时间单位,这是一个枚举,常用的有TimeUnit、MILLISECONDS、TimeUnit.SECONDS以及TimeUnit.MINUTES等。
  • workQueue:线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中。
  • threadFactory:线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,它只有一个方法:Thread new Thread(Runnable r)。

除了上面这些主要参数,ThreadPoolExecutor还有一个不常用的参数 RejectedExecutionHandler handler。当线程池无法执行新任务时,可能是由于任务队列已满或者是无法成功执行任务,这个时候ThreadPoolExecutor会调用handler的rejectedExecution方法来通知调用者。默认会抛出RejectedExecutionException。ThreadPoolExecutor为RehectedExecutionHandler提供了几个可选值:CallerRunsPolicy、AbortPolicy、DiscardPolicy和DiscardOldestPolicy,其中AbortPolicy是默认值。

ThreadPoolExecutor执行任务时遵循规则

  • 如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。
  • 如果线程池汇总的线程数量已经达到或者超过核心线程的数量,那么任务会被直接插入到任务队列汇总排队等待执行。
  • 如果无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会启动一个非核心线程来执行任务。
  • 如果线程数量达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。

例:
在这里插入图片描述

ThreadPoolExecutor分为四类:FixedThreadPool、CachedThreadPool、SheduledThreadPool以及SInglesThreadExecutor。

FixedThreadPool

通过Executors的newFixedThreadPool来创建,它是一种线程数量固定的线程池,当线程处于空闲状态时,它们并不会被回收,除非线程池被关闭了。当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来。由于FixedThreadPool只有核心线程,且这些核心线程不会被回收,这意味着它能够更快速地响应外界的请求。newFiexedThreadPool方法的实现如下,可以发现FixedThreadPool中只有核心线程并且这些核心线程没有超时机制,另外任务队列也是没有大小限制。
在这里插入图片描述

CachedThreadPool

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

线程池中的空闲线程都有超时机制,这个超时时长为60s,超过则会使得闲置线程就会被回收。和FiexedThreadPool不同的是,CachedThreadPool的任务队列其实相当于一个空集合,这导致任何任务都会被立即执行,适合执行大量的耗时较少的任务,当整个线程池都出闲置状态时,线程池中的线程都会超时而被停止,这个时候CachedThreadPool之中实际上是没有任何线程的,它几乎不占用任何系统资源。
在这里插入图片描述

ScheduledThreadPool

通过Executors的newScheduledPool方法来创建。它的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程限制时会被立即回收。ScheduledThreadPool这类线程池,主要用于执行定时任务和具有固定周期的重复任务。
在这里插入图片描述

SingleThreadExecutor

通过Executors的newSingleThreadExecutor方法来创建。这类线程池内部只有一个核心线程,确保所有的任务都在同一个线程中按顺序执行。SingleThreadExecutor的意义在于统一所有的外界任务到一个线程中,这使得在这些任务间不需要处理线程同步的问题。
在这里插入图片描述
4种线程池的使用例子
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值