Android的线程和线程池

前言

​ 每一个Android程序正常情况下都会启动在一个独立的进程中,而这个进程默认会启动一条线程用于处理和界面相关的事情,所以我们又称该线程为主线程(UI线程)。如果我们在主线程执行耗时相关的操作的话就会导致程序无法及时相应,因此耗时操作要放在子线程中去执行。

​ 其中new Thread()是Android中最简单的线程开启方式,但其中却包含了不少的隐患:

  1. 仅简单启动一个新线程,无法进行管理导致不能应对复杂的场景。
  2. Runnable做为匿名内部类,一旦持有外部引用,那么在线程退出之前,引用会一直存在,导致一段时间内造成内存泄漏。
  3. 如果处理结果需要到主线程的话,需要在里面写更多的代码进行线程的切换。
  4. 如果从主线程中启动线程,那么该线程优先级默认为Default,与主线程平级,即会与其一同争夺CPU资源。
new Thread(new Runnable() {
            @Override
            public void run() {

            }
        }).start();

​ 线程是一种受限的系统资源,是不可能无限地产生,并且线程的创建和销毁都会有相应的开销。所以对于线程的使用,应对不同场景应该采用不同的策略。Android系统为我们提供了如下一些方法来应对:

  • AsyncTask
  • HandlerThread
  • IntentService
  • ThreadPool

AsyncTask

​ 一个轻量级异步任务类,在线程池内执行任务,执行进度和最终结果在主线程中更新。

AsyncTask是一个抽象的泛型类

public abstract class AsyncTask<Params, Progress, Result>

提供了如下参数:

  • Params 表示参数类型
  • Progress 表示后台执行进度
  • Result 表示任务返回结果的类型

并且提供了4个核心的方法:

  • onPreExecute() 在主线程中运行,在异步任务执行之前,一般用于做一些准备工作
  • doInBackground(Params...params) 在线程池中执行,用于异步任务的执行。可以在此方法中通过publishProgress方法来更新进度,它会调用onProgressUpdate方法.该方法需要返回结果给onPostExecute方法
  • onProgressUpdate(Progress...values) 在主线程中执行,后台任务执行进度发送改变时会被调用
  • onPostExecute(Result result) 主线程中执行,异步任务执行完后,被调用,其中result参数是后台任务执行完的返回值。

除上述以外,还有一个onCancelled方法,调用该方法可以取消异步任务, onPostExecute(Result result) 方法将不会被调用,但是doInBackground(Params...params) 还是会继续执行。所以要真正地停止异步任务的执行,我们可以在doInBackground(Params...params) 里判断任务是否已经取消。例如:

private class TestAsyncTask extends AsyncTask<String,Void,String>{
        @Override
        protected String doInBackground(String... params) {
            for(int i = 0; i < params.length; i++) {
                //do anything

                if (isCancelled()) break;  //跳出循环
            }
            return "success";
        }
}

AsyncTask在使用中存在以下一些问题:

1)在Android 4.1以下版本,AsyncTask的类必须在主线程中加载

2) AsyncTask的实例必须在主线程中创建

3) execute方法必须在主线程中调用

4) 不要手动的调用onPreExecute(),doInBackground(Params…params),onProgressUpdate(Progress…values),onPostExecute(Result result) 这几个方法

5) AsyncTask只能被执行一次,否则多次调用时将会出现异常

6) 在Android1.6之前,AsyncTask是串行执行;之后AsyncTask开始采用线程池处理并行任务;但从Android3.0开始,为了避免并发错误,AsyncTask又采用一个线程来串行执行任务,不过我们仍然可以通过AsyncTask的executeOnExecutor来并行执行任务

HandlerThread

​ 继承至Thread,是一个可以使用Handler的Thread。

它在run方法中通过Looper.prepare()来创建消息队列,Looper.loop()开启消息循环,使得我们可以在实际使用中在HandlerThread中创建Handler了。

   public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

与Thread区别在于,使用Thread时是直接通过run方法来执行一个耗时任务。而HandlerThread是在内部创建了一个消息队列,外界需要通过Handler的消息方式来通知它执行一个具体的任务。由于HandlerThread的run方法是一个无线循环,所以当明确不需要使用时,可以通过它的quit或者quitSafely方法来终止线程的执行。

IntentService

​ 是一个继承于Service的抽象类。可以用于执行后台耗时的任务,并且在任务执行完毕后会自动停止。由于它是Service的原因,所以它的优先级比单纯的线程要高很多,适合执行一些高优先级的后台任务。

​ 通过继承IntentService,我们需要实现onHandleIntent(Intent intent)方法,我们可以在该方法中执行一些耗时任务。

Threadpool

线程池有着如下优点:

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

ThreadPoolExecutor

Android中的线程池是来自Java中的Executor,Executor是一个接口,其实现为ThreadPoolExecutor,它的构造方法提供了一系列的参数来配置线程。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue)
  • corePoolSize 线程池维护线程的最少数量(核心线程数量),默认情况下,核心线程会在线程池中一直存活,即使它们处于闲置状态。如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来时会有超市策略,这个时间间隔由keepAliveTime所指定,当等待时间超过keepAliveTime所指定的时长后,核心线程就会被终止。

  • maximumPoolSize 线程池维护线程的最大数量,当活动线程达到这个数值后,后续的新人无语将会被阻塞。

  • keepAliveTime 线程池中超过corePoolSize数目的空闲线程最大存活时间;可以allowCoreThreadTimeOut(true)使得核心线程有效时间。

  • unit 用于指定keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

    TimeUnit.DAYS; //天

    TimeUnit.HOURS; //小时

    TimeUnit.MINUTES; //分钟

    TimeUnit.SECONDS; //秒

    TimeUnit.MILLISECONDS; //毫秒

    TimeUnit.MICROSECONDS; //微妙

    TimeUnit.NANOSECONDS; //纳秒

  • workQueue 线程池所使用的缓冲队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中。

ThreadPoolExecutor执行规则:

  1. 当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程
  2. 当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
  3. 当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
  4. 当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
  5. 当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
  6. 当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭 。

常见的四种线程池

  1. FixedThreadPool 该方法返回一个固定线程数量的线程池,该线程池中的线程数量始终不变,即不会再创建新的线程,也不会销毁已经创建好的线程,自始自终都是那几个固定的线程在工作,所以该线程池可以控制线程的最大并发数。

  2. CachedThreadPool 该方法返回一个可以根据实际情况调整线程池中线程的数量的线程池。即该线程池中的线程数量不确定,是根据实际情况动态调整的。

  3. SingleThreadExecutor 该方法返回一个只有一个线程的线程池,即每次只能执行一个线程任务,多余的任务会保存到一个任务队列中,等待这一个线程空闲,当这个线程空闲了再按FIFO方式顺序执行任务队列中的任务。

  4. ScheduledThreadPool 该方法返回一个可以控制线程池内线程定时或周期性执行某任务的线程池。

版权声明:

本文标题:Android的线程和线程池

作者:Rabtman

原始链接:https://rabtman.com/2016/08/19/note_android_thread/

本文采用署名-非商业性使用-禁止演绎4.0进行许可。

非商业转载请保留以上信息。商业转载请联系作者本人。