欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android线程及线程池详情

程序员文章站 2022-04-28 13:24:56
线程在android中的地位,相信每一位开发者都理解。基于android的特性,ui线程即主线程主要用于界面的更新,子线程用于进行耗时任务。通过本篇文章,将学习主线程以及子线程的概念,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的基本用法:

创建asynctask
class 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 asynctask execute(params... params) {
567        return executeonexecutor(sdefaultexecutor, params);
568    },>

execute方法会调用executeonexecutor方法:

603    @mainthread
604    public final asynctask executeonexecutor(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
该线程池内只有一个核心线程,并且确保所有任务都在同一个任务中执行,这样多任务间就不需要解决同步的问题。