概述
-
操作系统中,线程是操作系统调度的最小单位,当系统中存在大量的线程时,系统会通过时间片转轮的方式调度每个线程,所以线程不可能做到绝对的并行。
采用线程池,一个线程池中会缓存一定数量的线程,通过线程池可以避免因为频繁创建和销毁线程带来的系统开销 -
java中默认情况下一个进程只有一个线程,是主线程。
主线程:处理界面交互相关逻辑,必须有较高的响应速度
子线程:也叫工作线程,除了主线程以外的线程 -
在Android中,几乎完全采用了Java中的线程机制
主线程:也叫UI线程,运行四大组件以及处理他们和用户的交互
子线程:执行耗时任务,如网络请求,I/O操作(Android3.0开始,系统要求网络请求必须在子线程中,避免主线程由于耗时操作所阻塞从而出现ANR现象)
Android除了传统的Thread外
AsyncTask,IntentService,HandlerThread本质都是传统的线程。AsyncTask底层用到了线程池,IntentService,HandlerThread底层直接使用了线程
AsyncTask:封装了线程池和Handler,方便开发者在子线程中更新UI
HandlerThread:具有消息循环的线程,在它内部可以使用Handler
IntentService:一个服务,系统对其封装使其可以更方便地执行后台任务。内部采用HandlerThread执行任务,当执行任务完毕后会自动退出。因为是一个服务,不容易被系统杀死,可以尽量保证任务地执行。(后台的线程,由于这个时候没有活动的四大组件,这个进程的优先级就很低,容易被系统杀死)
1. Android中的线程形态——AsyncTask
轻量级的异步任务类,可在线程池中执行后台任务,然后把执行进度和最终结果传递给主线程并在主线程中更新UI。AsyncTask封装了Thread和Handler,通过它可以更加方便地执行后台任务以及在主线程中访问UI。不适合执行特别耗时地任务。
AsyncTask是一个抽象泛型类
public abstract class AsyncTask<Params, Progress, Result>
Params :参数类型,启动任务执行的输入参数,如HTTP请求的URL
Progress : 后台任务的执行进度的类型,后台任务执行的百分比
Result :后台执行任务最终返回的结果类型
如果AsyncTask不需要传递具体参数,那么这三个泛型参数可以使用Void代替
4个核心方法:
(1)onPreExecute(): 该方法在主线程中执行,将在execute(Params… params)被调用后执行,一般用来做一些UI的准备工作,如在界面上显示一个进度条。
(2)doInBackground(Params…params):抽象方法,必须实现,该方法在线程池中执行,用于执行异步任务,将在onPreExecute方法执行后执行。其参数是一个可变类型,表示异步任务的输入参数,在该方法中还可通过publishProgress(Progress… values)来更新实时的任务进度,而publishProgress方法则会调用onProgressUpdate方法。此外doInBackground方法会将计算的返回结果传递给onPostExecute方法。
(3)onProgressUpdate(Progress…):在主线程中执行,该方法在publishProgress(Progress… values)方法被调用后执行,一般用于更新UI进度,如更新进度条的当前进度。
(4)onPostExecute(Result):在主线程中执行,在doInBackground 执行完成后,onPostExecute 方法将被UI线程调用,doInBackground 方法的返回值将作为此方法的参数传递到UI线程中,并执行一些UI相关的操作,如更新UI视图。
(5)onCancelled():在主线程中执行,当异步任务被取消时,该方法将被调用,要注意的是这时onPostExecute将不会被执行。
onPreExecute方法先执行,接着是doInBackground方法,在doInBackground中如果调用了publishProgress方法,那么onProgressUpdate方法将会被执行,最后doInBackground方法执行后完后,onPostExecute方法将被执行。
private class DownloadFilesTask extends AsyncTask<URL,Integer,Long>{
protected Long doInBackground(URL... urls){ //返回结果,下载的总字节数
//在线程池中执行
int count = urls.length;
long totalSize = 0;
for(int i=0;i<count;i++){
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int)((i/(float)count)*100));
if(isCancelled())
break;;
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress){ //更新界面中下载的进度
//运行在主线程
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result){ //下载任务完成后
//运行在主线程
showDialog("Downloaded "+ result+" bytes");
}
}
这个类主要用于模拟文件的下载过程,当要执行下载任务时:
new DownloadFilesTask().execute(url1,url2,url3);
注意事项:
(1) AsyncTask的实例必须在主线程(UI线程)中创建 ,第一次访问AsyncTask必须在主线程,AsyncTask对象必须在主线程中创建,execute方法也必须在主线程中调用
(2) 不要在程序中直接的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)这几个方法
(3) 不能在doInBackground(Params… params)中更新UI
(5) 一个AsyncTask对象只能被执行一次,也就是execute方法只能调用一次,否则多次调用时将会抛出异常
(6)在android 3.0之前,AsyncTask处理任务时默认采用的是线程池里并行处理任务的方式,而在android 3.0之后 ,为了避免AsyncTask处理任务时所带来的并发错误,AsyncTask则采用了单线程串行执行任务。但是这并不意味着android 3.0之后只能执行串行任务,我们仍然可以采用AsyncTask的executeOnExecutor方法来并行执行任务。
2.HandlerThread
1.HandlerThread本质是一个线程,继承了 Thread,是一种可以使用Handler的Thread
2.实现:在run方法中通过Looper.prepare()来创建消息队列,通过Looper.loop()开启消息循环,这样在实际的使用中就允许在HandlerThread中创建Handler了
HandlerThread的run方法:
public void run() {
mTid = Process.myTid();
Looper.prepare(); //创建消息队列
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop(); //开启消息循环
mTid = -1;
}
- 普通Thread:主要用于在run方法中执行一个耗时任务
HandlerThread:在内部创建了消息队列,外界需要通过Handler的消息方式通知HandlerThread执行一个具体任务。 - Handler在Android中一个具体使用场景是IntentService
- HandlerThread的run方法是一个无限循环,当明确不使用HandlerThread时,可通过它的quit或quitSafely方法来终止线程的执行
- 通过获取HandlerThread的looper对象传递给Handler对象,可以在handleMessage方法中执行异步任务
- 创建HandlerThread后必须先调用HandlerThread.start()方法,Thread会先调用run方法,创建Looper对象
2.1 使用步骤
1.创建实例对象
HandlerThread handlerThread = new HandlerThread("downloadImage");
2.启动HandlerThread线程
handlerThread.start();
3.构建循环消息处理机制
将一个耗时的异步任务投放到HandlerThread线程中去执行
/**
* 该callback运行于子线程
*/
class ChildCallback implements Handler.Callback {
@Override
public boolean handleMessage(Message msg) {
//在子线程中进行相应的网络请求
//通知主线程去更新UI
mUIHandler.sendMessage(msg1);
return false;
}
}
4.构建异步handler
//子线程Handler
Handler childHandler = new Handler(handlerThread.getLooper(),new ChildCallback());
第3步和第4步是构建一个可以用于异步操作的handler,并将前面创建的HandlerThread的Looper对象以及Callback接口类作为参数传递给当前的handler,这样当前的异步handler就拥有了HandlerThread的Looper对象,由于HandlerThread本身是异步线程,因此Looper也与异步线程绑定,从而handlerMessage方法也就可以异步处理耗时任务了,这样我们的Looper+Handler+MessageQueue+Thread异步循环机制构建完成
3.IntentService
- IntentService 继承了 Service ,是一个抽象类,必须创建它的子类才能使用。
- 可用于执行后台耗时的任务,执行后会自动停止
- 是服务,优先级比单纯的线程高多,适合执行一些高优先级的后台任务
- 内部通过HandlerThread和Handler实现异步操作
IntentService被第一次启动时,它的onCreate方法会被调用,onCreate方法会创建一个HandlerThread,然后使用它的Looper构造一个Handler对象mServiceHandler,这样通过mServiceHandler发送的消息最终都会在HandlerThread中执行。从这个角度看,IntentService可用于执行后台任务。
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
每次启动IntentService时,onStartCommand方法会被调用一次,onStartCommand调用了onStart()
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
IntentService通过mServiceHandler发送一个消息,这个消息在HandlerThread中被处理,mServiceHandler收到消息后,将Intent对象传递给onHandlerIntent方法去处理,这个Intent对象的内容和外界的startService(intent)中的intent内容一样,通过这个Intent对象可解析出外界启动IntentSevice时传递的参数,通过这些参数区分具体的后台任务,这样在onHandlerIntent方法中就可以对不同的后台任务做处理了。
IntentService通过stopSelf() / stopSelf(int startId) 停止服务
stopSelf():立刻停止服务
stopSelf(int startId):等待所有消息都处理完毕后才终止服务
每执行一个后台任务就必须启动一次IntentService,IntentService内部通过消息的方式向HandlerThread请求执行任务,Handler中的Looper是顺序处理消息的,意味着IntentService也是顺序执行后台任务的
3. Android中的线程池
线程池的优点:
- 降低资源消耗:重用线程池的线程,避免因为线程的创建和销毁带来的性能开销
- 提高响应速度:能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占资源而导致的阻塞现象。
- 提高线程的可管理性: 对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能
3.1 ThreadPoolExecutor
3.1.1 ThreadPoolExecutor参数
真正的线程池的实现为ThreadPoolExecutor,ThreadPoolExecutor的构造方法提供了一系列参数来配置线程池,通过不同的参数可以创建不同的线程池
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
-
int corePoolSize(核心线程数)
线程池的核心线程数,默认情况核心线程会一直存活在线程池中,即使处于闲置状态;
如果设置了 allowCoreThreadTimeOut 为 true,那么核心线程如果不干活(闲置状态)的话,超过一定时间会被销毁掉。时间间隔由keepAliveTime指定。 -
int maximumPoolSize(线程池能容纳的最大线程数量)
线程池所能容纳的最大线程数,线程总数 = 核心线程数 + 非核心线程数。当活动线程数达到这个数值后后续的任务会被阻塞 -
long keepAliveTime(非核心线程空闲存活时长)
非核心线程空闲存活时长,非核心线程空闲时长超过该时长将会被回收 -
TimeUnit unit(keepAliveTime 的单位)
它是一个枚举类型,常用的如:TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒)、TimeUnit.MINUTES(分钟) -
BlockingQueue workQueue(任务队列)
通过线程池的execute方法提交的Runnable对象会存储在这个参数中。
常用的workQueue类型:- SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现 线程数达到了 maximumPoolSize 而不能新建线程 的错误,使用这个类型队列的时候,maximumPoolSize 一般指定成 Integer.MAX_VALUE,即无限大。
- LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了 maximumPoolSize 的设定失效,因为总线程数永远不会超过 corePoolSize。
- ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到 corePoolSize 的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了 maximumPoolSize,并且队列也满了,则发生错误。
- DelayQueue:队列内元素必须实现 Delayed 接口,这就意味着你传进去的任务必须先实现 Delayed 接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务。
-
ThreadFactory threadFactory(线程工厂)
线程工厂,用来创建线程池中的线程 -
RejectedExecutionHandler handler(拒绝策略)
在线程池已经关闭的情况下和任务太多导致最大线程数和任务队列已经饱和,无法再接收新的任务,在上面两种情况下,只要满足其中一种时,在使用 execute() 来提交新的任务时将会拒绝。
3.1.2 ThreadPoolExecutor 遵循的规则
- 线程池的线程数量未达到核心线程的数量,直接启动一个核心线程执行任务
- 线程数量达到了 corePoolsSize,任务被插入到任务队列中排队等待执行
- 步骤2中无法将任务插入到任务队列,队列已满,若线程数量未达到线程池规定的最大值,立刻启动一个非核心线程执行任务
- 步骤3中线程数量已经达到线程池规定的最大值 maximumPoolSize,拒绝执行此任务,ThreadPoolExecutor调用 RejectedExecutionHandler的rejectedExecution方法通知异常。
3.2 线程池的分类