Android线程及线程池详情
线程在android中的地位,相信每一位开发者都理解。基于android的特性,ui线程即主线程主要用于界面的更新,子线程用于进行耗时任务。通过本篇文章,将学习主线程以及子线程的概念,android中的线程形态,包括我们熟悉的asynctask、handlerthread、intentservice,最后,认识线程池在android中的应用,以及主要的线程池分类。
主线程和子线程
主线程是指进程所拥有的线程,java中,默认情况下一个进程中只有一个线程,即主线程。主线程在任何时候都必须保持高度的流畅性,以应对用户与ui界面的交互。为了主线程的保持较高的响应速度,子线程就派上用场了。通常将比较耗时的操作放在子线程中执行。除了主线程之外的所有线程都叫子线程。android沿用了java的线程机制,将线程分为ui线程和子线程,ui线程即主线程。ui线程运行四大以及它们与用户的交互,子线程执行比较耗时的操作,类似文件读写,网络请求等。
android中的线程形态
我们知道,我们在实际的开发中,创建一个线程有多种方式,下面将从asynctask、handlerthread、intentservice三者来对线程形态进行介绍。三者的底层实现都采用的是线程,但具有不同的表现形态,在使用中也各有优缺点。
1、asynctask
asynctask是一个轻量级的异步类,它可以在线程池中执行后台任务,并把执行任务进度以及结果返回给主线程,并更新ui。从实现上来说,asynctask内部封装了thread和handler,可以很方便的执行后台任务和在主线程中更新ui。但是,asynctask不适合进行比较耗时的后台任务,下面先介绍asynctask的基本用法:
创建asynctaskclass myasynctask extends asynctask{ @override protected void onpreexecute() { super.onpreexecute(); log.d("myasynctask","do onpreexecute"); } @override protected integer doinbackground(string... files) { log.d("myasynctask","do doinbackground"); integer cont = 0; for (string file : files){ cont ++; log.d("myasynctask","do file:" + file); publishprogress(cont); } return cont; } @override protected void onprogressupdate(integer... values) { super.onprogressupdate(values); for (integer integer : values){ log.d("myasynctask","do onprogressupdate:" + integer.intvalue()); } } @override protected void oncancelled() { super.oncancelled(); } @override protected void onpostexecute(integer integer) { super.onpostexecute(integer); log.d("myasynctask","do onprogressupdate:" + integer); } }开启asynctask
string[] files = {"file1","file2","file3"}; new myasynctask().execute(files);
从上面的代码可以知道,首先我们创建一个内部类,继承asynctask,并重写其相关方法。然后创建asynctask的对象实例,通过execute方法开启线程。asynctask类有三个参数params,progess,result,三个参数都为泛型。params是我们传入线程的参数,比如网络url,文件路径等,可以包含多个;progess为线程在执行过程中返回的进度;result为线程执行完后返回的结果。
onpreexecute:在主线程中执行,在异步线程开始之前被调用,一般做一些准备的工作。 doinbackground:在线程池中执行,此方法用于执行异步任务。通过publishprogress方法更新任务进度,publishprogress方法会调用onprogressupdate方法。任务执行完后,会返回结果给onpostexecute。 onprogressupdate:主线程中执行,在后台任务进度改变时被调用。 onpostexecute:在主线程中执行,异步线程任务执行完成后该方法被调用,参数为后台任务的返回值,即doinbackground的返回值。 oncancelled:主线程中执行,当后台任务被取消时,该方法被调用,onpostexecute方法将不再被调用。asynctask方便了我们应用,但在使用中有一些限制:
必须在主线程中创建和加载; execute方法必须在ui线程调用; 不要在程序中直接调用onpreexecute、doinbackground、onprogressupdate、onpostexecute方法; 一个asynctask对象只能调用一次,即只能执行一次execute方法,否则会报异常; asynctask采用一个线程来串行执行任务,可以通过其executeonexecutor方法来并行的执行任务。上面介绍了asynctask的用法以及其在使用中应该注意的情况,下面通过分析asynctask的实现原理,就会比较理解asynctask的相关限制。
我们从execute方法开始分析:
@mainthread 566 public final asynctaskexecute(params... params) { 567 return executeonexecutor(sdefaultexecutor, params); 568 } ,>
execute方法会调用executeonexecutor方法:
603 @mainthread 604 public final asynctaskexecuteonexecutor(executor exec, 605 params... params) { 606 if (mstatus != status.pending) { 607 switch (mstatus) { 608 case running: 609 throw new illegalstateexception("cannot execute task:" 610 + " the task is already running."); 611 case finished: 612 throw new illegalstateexception("cannot execute task:" 613 + " the task has already been executed " 614 + "(a task can be executed only once)"); 615 } 616 } 617 618 mstatus = status.running; 619 620 onpreexecute(); 621 622 mworker.mparams = params; 623 exec.execute(mfuture); 624 625 return this; 626 } ,>
上面execute方法中的sdefaultexecutor是一个串行的线程池,一个进程中所有的asynctask都在该线程池中排队执行。在executeonexecutor方法中,首先会对当前状态进行判断,处于runing、finshed状态都会抛出异常,只有在pending状态才向下执行。onpreexecute方法会被最先调用,然后开始后台任务。
public static final executor serial_executor = new serialexecutor(); private static volatile executor sdefaultexecutor = serial_executor; private static class serialexecutor implements executor { 236 final arraydeque mtasks = new arraydeque(); 237 runnable mactive; 238 239 public synchronized void execute(final runnable r) { 240 mtasks.offer(new runnable() { 241 public void run() { 242 try { 243 r.run(); 244 } finally { 245 schedulenext(); 246 } 247 } 248 }); 249 if (mactive == null) { 250 schedulenext(); 251 } 252 } 253 254 protected synchronized void schedulenext() { 255 if ((mactive = mtasks.poll()) != null) { 256 thread_pool_executor.execute(mactive); 257 } 258 } 259 }
会将params参数封装成futuretask对象,futuretask是一个并发对象,在这里充当runnable对象。futuretask会被交给serialexecutor 的execute方法处理,将futuretask插入到任务队列mtasks中。执行完任务后,会调用schedulenext来开启下一个asynctask,直到所有任务被执行完为止。从这里可以看出,asynctask是串行执行的。
asynctask中有两个线程池(serialexecutor 、thread_pool_executor)和一个handler(internalhandler)。serialexecutor 线程池用于任务的排队,thread_pool_executor则执行真正的后台任务。internalhandler用于将执行环境切换到主线程。再asynctask的构造方法在中,有如下代码:
mworker = new workerrunnable() { 299 public result call() throws exception { 300 mtaskinvoked.set(true); 301 result result = null; 302 try { 303 process.setthreadpriority(process.thread_priority_background); 304 //noinspection unchecked 305 result = doinbackground(mparams); 306 binder.flushpendingcommands(); 307 } catch (throwable tr) { 308 mcancelled.set(true); 309 throw tr; 310 } finally { 311 postresult(result); 312 } 313 return result; 314 } 315 }; ,>
futuretask会调用mworker 的run方法,因此mwork最终将在线程池中执行。再mwork的的call方法中,首先将mtaskinvoked设置true,表示任务已经被调用,然后执行doinbackground方法,最终将结果返回给postresult方法。
private result postresult(result result) { 342 @suppresswarnings("unchecked") 343 message message = gethandler().obtainmessage(message_post_result, 344 new asynctaskresult(this, result)); 345 message.sendtotarget(); 346 return result; 347 }
在postresult方法中,通过gethandler方法获取shandler 对象并发送一条message_post_result消息。
private static handler gethandler() { 281 synchronized (asynctask.class) { 282 if (shandler == null) { 283 shandler = new internalhandler(); 284 } 285 return shandler; 286 } 287 }
该handler定义如下:
private static class internalhandler extends handler { 673 public internalhandler() { 674 super(looper.getmainlooper()); 675 } 676 677 @suppresswarnings({"unchecked", "rawuseofparameterizedtype"}) 678 @override 679 public void handlemessage(message msg) { 680 asynctaskresult result = (asynctaskresult) msg.obj; 681 switch (msg.what) { 682 case message_post_result: 683 // there is only one result 684 result.mtask.finish(result.mdata[0]); 685 break; 686 case message_post_progress: 687 result.mtask.onprogressupdate(result.mdata); 688 break; 689 } 690 } 691 }
可以看到,该handler是一个静态对象,为了将执行环境切换到主线程,因此shandler 必须在主线程创建。由于静态成员在类加载的时候会被初始化,因此这里变相的要求asynctask必须在主线程创建。收到message_post_result消息后,会调用finish方法。
private void finish(result result) { 664 if (iscancelled()) { 665 oncancelled(result); 666 } else { 667 onpostexecute(result); 668 } 669 mstatus = status.finished; 670 }
在finish方法中,如果iscancelled方法被调用,即任务被取消,则调用oncancelled(result)方法,反之调用onpostexecute方法,参数为doinbackground方法的返回结果。
下面通过一个例子,验证asynctask的串行执行。
点击按钮时,同时开启五个任务,每个任务休眠5秒模拟耗时,最后打印任务执行完成的时间点。
mbutton.setonclicklistener(new view.onclicklistener() { @override public void onclick(view view) { new testtask("task1").execute(); new testtask("task2").execute(); new testtask("task3").execute(); new testtask("task4").execute(); new testtask("task5").execute(); } }); class testtask extends asynctask{ private string mname = "testtask"; public testtask(string mname) { this.mname = mname; } @override protected string doinbackground(string... strings) { try { thread.sleep(5000); } catch (interruptedexception e) { e.printstacktrace(); } return mname; } @override protected void onpostexecute(string s) { super.onpostexecute(s); simpledateformat time = new simpledateformat("yyyy-mm-dd hh:mm:ss"); log.d("testtask",s + " finshed at time:" + time.format(new date())); } }
结果:
d/testtask: task1 finshed at time:2018-01-31 02:37:37 d/testtask: task2 finshed at time:2018-01-31 02:37:42 d/testtask: task3 finshed at time:2018-01-31 02:37:47 d/testtask: task4 finshed at time:2018-01-31 02:37:52 d/testtask: task5 finshed at time:2018-01-31 02:37:57
通过结果,我们可以再次验证asynctask在线程池中是串行执行的,当然,这是在android3.0之后,之前将并行执行。为了能在3.0后并发执行,系统提供了executeonexecutor方法。
new testtask("task1").executeonexecutor(asynctask.thread_pool_executor,""); new testtask("task2").executeonexecutor(asynctask.thread_pool_executor,""); new testtask("task3").executeonexecutor(asynctask.thread_pool_executor,""); new testtask("task4").executeonexecutor(asynctask.thread_pool_executor,""); new testtask("task5").executeonexecutor(asynctask.thread_pool_executor,"");
结果:
d/testtask: task2 finshed at time:2018-01-31 02:59:26 d/testtask: task1 finshed at time:2018-01-31 02:59:26 d/testtask: task3 finshed at time:2018-01-31 02:59:31 d/testtask: task4 finshed at time:2018-01-31 02:59:31 d/testtask: task5 finshed at time:2018-01-31 02:59:36
上面的代码运行在api 26上,发现通过executeonexecutor方法开启并发任务时,最多允许两个asynctask任务并发进行。当我们调用execute方法时,系统会通过serialexecutor 线程池对任务进行排队,最后调用executeonexecutor串行执行;当我们直接调用executeonexecutor方法是,跳过了中间的排队过程,直接通过thread_pool_executor线程池开启并发任务。
对于asynctask实现原理的分析就到这里了,总结一下:在asynctask中,一个两个线程池和一个handler。serialexecutor 线程池用于任务的排队,thread_pool_executor线程池用于执行具体的任务,当任务完成后,通过internalhandler发送结果消息,并将执行环境切换到主线程。internalhandler为静态对象,当加载类时,会自动初始化,这样就变相的要求asynctask在主线程创建。
2、handlerthread
handlerthread继承thread,可以使用handler的线程。在其run方法中,通过looper.prepare()创建消息循环队列,looper.loop()开启消息循环。有了looper对象,这样就可以在handlerthread中创建handler对象。与常规thread不同的是,一般的thread通常执行一些耗时任务,而handlerthread内部封装了消息循环,外界需要通过handler通知handlerthread执行具体的任务。handlerthread的run方法是一个死循环,因此当不再需要的时候,调用其 quit()或 quitsafely()方法跳出循环,终止线程的执行。
@override 52 public void run() { 53 mtid = process.mytid(); 54 looper.prepare(); 55 synchronized (this) { 56 mlooper = looper.mylooper(); 57 notifyall(); 58 } 59 process.setthreadpriority(mpriority); 60 onlooperprepared(); 61 looper.loop(); 62 mtid = -1; 63 }
3、intentservice
intentservice是一种特殊的service,并且是一个抽象类,因此必须为其创建子类才能实现它。intentservice用于执行后台任务,当任务执行完成后会自动停止。由于其属于server,所以优先级会高于普通的线程,比较适合开启优先级较高的后台任务,不容易因系统内存不足而被杀死。实现上,intentservice封装了handlerthread和handler,二者在oncreate方法中被创建:
@override 104 public void oncreate() { 105 // todo: it would be nice to have an option to hold a partial wakelock 106 // during processing, and to have a static startservice(context, intent) 107 // method that would launch the service & hand off a wakelock. 108 109 super.oncreate(); 110 handlerthread thread = new handlerthread("intentservice[" + mname + "]"); 111 thread.start(); 112 113 mservicelooper = thread.getlooper(); 114 mservicehandler = new servicehandler(mservicelooper); 115 }
第一次启动intentserver时,oncreate方法会被调用,首先创建handlerthread 线程对象并开启线程。然后获取该线程的looper,并创建对应的mservicehandler ,这样,由mservicehandler 发送的消息都将被handlerthread 线程处理执行。每次启动intentserver,它的onstartcommand会被调用,用于处理每个后台任务的intent。
@override 132 public int onstartcommand(@nullable intent intent, int flags, int startid) { 133 onstart(intent, startid); 134 return mredelivery ? start_redeliver_intent : start_not_sticky; 135 }
onstartcommand会调用onstart方法:
@override 118 public void onstart(@nullable intent intent, int startid) { 119 message msg = mservicehandler.obtainmessage(); 120 msg.arg1 = startid; 121 msg.obj = intent; 122 mservicehandler.sendmessage(msg); 123 }
可以看出,在onstart方法中,通过mservicehandler发送了intent对象消息,因为mservicehandler是在handlerthread 中创建,所以消息会在handlerthread 线程中被处理。mservicehandler的handlemessage方法中,会将收到的intent对象交给 onhandleintent方法处理。值得注意的是,这里的intent对象和启动server时startserver(intent)里的intent对象是一致的,这样在onhandleintent方法中通过intent的不同参数开启不同的任务。
private final class servicehandler extends handler { 62 public servicehandler(looper looper) { 63 super(looper); 64 } 65 66 @override 67 public void handlemessage(message msg) { 68 onhandleintent((intent)msg.obj); 69 stopself(msg.arg1); 70 } 71 }
其中,onhandleintent方法是一个抽象方法,需要在子类中实现。当onhandleintent方法执行完后,系统会调用stopself(msg.arg1)来尝试停止服务。之所以用stopself(msg.arg1)而不是用stopself()方法是因为当前可能还有其他消息未处理,而stopself()方法会立即停止服务,stopself(msg.arg1)方法会等待所有消息处理完再停止服务。
由于每执行一次任务就必须启动一次intentserver,而intentserver内部是通过消息机制向handlerthread 请求执行任务的,handler中的looper是顺序处理消息的,因此,intentserver也将顺序执行后台任务。
下面通过一个举例,来阐述intentserver的用法。
public static class servertask extends intentservice{ public servertask() { super("test"); } @override protected void onhandleintent(@nullable intent intent) { string tag = intent.getstringextra("action"); log.d("servertask","action: " + tag); try { thread.sleep(5000); } catch (interruptedexception e) { e.printstacktrace(); } } @override public void ondestroy() { super.ondestroy(); log.d("servertask","ondestroy--------"); } } intent intent = new intent(this,servertask.class); intent.putextra("action","action_tag 1#"); startservice(intent); intent.putextra("action","action_tag 2#"); startservice(intent); intent.putextra("action","action_tag 3#"); this.startservice(intent);
在onhandleintent方法中获取intent的参数值,可以对不同的intent进行过滤,执行不同操作。最后在ondestroy方法中进行日志打印,判断service自动结束的时间点。
结果打印:
d/servertask: action: action_tag 1# d/servertask: action: action_tag 2# d/servertask: action: action_tag 3# d/servertask: ondestroy--------
从日志可以看出,任务执行的顺序是串行的,当所有的任务执行完成后,ondestroy方法被调用,服务停止。
线程池
提到线程池,首先说一下线程池的优点:
重用线程池中的线程,避免创建或销毁线程带来的性能开销; 能有效控制线程池的最大并发数,避免大量线程间相互抢占资源导致的阻塞现象; 能够对线程进行简单的管理,并提供定时执行或指定间隔循环执行。android中的线程池来至与java中的executor,executor是一个接口,真正的线程池实现为threadpoolexecutor。threadpoolexecutor通过不同的参数来创建不同功能的线程池。
threadpoolexecutor的构造方法中,通过不同的参数配置线程池,下面通过threadpoolexecutor中的不同参数,来理解线程池:
public threadpoolexecutor(int corepoolsize, 1182 int maximumpoolsize, 1183 long keepalivetime, 1184 timeunit unit, 1185 blockingqueue workqueue) { 1186 this(corepoolsize, maximumpoolsize, keepalivetime, unit, workqueue, 1187 executors.defaultthreadfactory(), defaulthandler); 1188 }
corepoolsize
线程池的核心线程数,默认情况下,核心线程会一直存活,即使处于闲置状态。当threadpoolexecutor的allowcorethreadtimeout属性设置为true时,则会有超时策略,时间由keepalivetime决定,当超过该时间时,核心线程将被终止。
maximumpoolsize
线程池能容纳最多的线程数,当达到该上限后,后续的任务将会被阻塞。
keepalivetime
非核心线程闲置状态超时时间,超过该时间,非核心线程将会被终止。当allowcorethreadtimeout属性设置为true时,同样作用于核心线程。
unit
用于指定keepalivetime时间单位,为一个枚举,包括毫秒、秒、分钟等
workqueue
线程池中的任务队列,通过线程池的execute方法提交的runnable对象会存储在该队列中。
threadfactory
线程工厂,为线程池提供创建新线程的接口。其只有一个方法,thread newthread(runnable r)。
除了上面的参数外,线程池还有一个特殊的参数rejected-executionhandler,当线程池无法执行任务时,有可能是线程任务已满或者是无法成功执行任务。这时候threadpoolexecutor会调用该handler的rejectedexeuction方法来通知调用者,默认情况下会抛出一个异常。
threadpoolexecutor执行任务时,遵循下面的规则:
如果线程池中的线程数量未达到核心线程数时,会开启一个新的核心线程来执行任务; 如果线程池中线程数量达到或超过核心线程数,则会被放入等待队列中排队执行; 上述二中,如果无法插入排队队列,那么一般情况是队列已满,此时如果线程数量未达到线程池规定的最大值,那么将开启非核心线程来执行任务; 上述三中,如果线程数量已经达到线程池规定的最大线程数,那么将拒绝执行该任务。上面介绍了线程池,那么接下来介绍几种比较常用的线程池。
fixedthreadpool这是一种线程数量固定的线程池,当线程处于空闲状态时,并不会被回收,除非线程池被销毁。当所有线程都处于活跃状态时,新任务将等待,知道有空闲任务出来。由于该种线程池只有核心线程并且核心线程不会被回收,意味着它能够加速外界的请求。 cachedthreadpool
这是一种线程数量不固定的线程池,并且只有非核心线程。最大线程数为integer.max_value,由于integer.max_value是一个很大的数,也就是说线程数可以任意大。当线程池中线程处于活跃状态时,会创建新的线程执行任务,否则将利用空闲线程,其中空闲线程的超时时间为一分钟。 schedulethreadpool
核心线程是固定的,而非核心线程是没有限制的,并且非核心线程在闲置时会立即被回收。 singlethreadexecutor
该线程池内只有一个核心线程,并且确保所有任务都在同一个任务中执行,这样多任务间就不需要解决同步的问题。
上一篇: 明明白白的工作安排