Android线程与线程池

引言

在Android中,几乎彻底采用了Java中的线程机制。线程是最小的调度单位,在不少状况下为了使APP更加流程地运行,咱们不可能将不少事情都放在主线程上执行,这样会形成严重卡顿(ANR),那么这些事情应该交给子线程去作,但对于一个系统而言,建立、销毁、调度线程的过程是须要开销的,因此咱们并不能无限量地开启线程,那么对线程的了解就变得尤其重要了。java


Thread/Runnable/Callable

通常实现线程的方法有两种,一种是类继承Thread,一种是实现接口Runnable。这两种方式的优缺点如何呢?咱们知道Java是单继承但能够调用多个接口,因此看起来Runnable更加好一些。程序员

继承Threadweb

class MyThread extend Thread(){ @Override public void run() { super.run(); Log.i(Thread.currentThread().getId()); } } new MyThread().start();

实现Runnable接口并发

class MyThread implements Runnable{
    @Override
    public void run() {
        Log.i("MyThread", Thread.currentThread().getName());
    }
}
MyThreaed myThread = new MyThread();
new Thread(myThread).start();

当咱们调用Thread时,会有两种方式:异步

Thread myThread = new Thread();
myThread.run();
myThread.start();

咱们应该知道,run()方法只是调用了Thread实例的run()方法而已,它仍然运行在主线程上,而start()方法会开辟一个新的线程,在新的线程上调用run()方法,此时它运行在新的线程上。async

Runnable只是一个接口,因此单看这个接口它和线程毫无瓜葛,可能一部分人会觉得Runnable实现了线程,这种理解是不对的。Thread调用了Runnable接口中的方法用来在线程中执行任务。ide

public interface Runnable {
    public void run();
}

public interface Callable<V> {
    V call() throws Exception;
}

Runnable 和 Callable 都表明那些要在不一样的线程中执行的任务。Runnable 从 JDK1.0 开始就有了,Callable 是在 JDK1.5 增长的。它们的主要区别是 Callable 的 call() 方法能够返回值和抛出异常,而 Runnable 的 run() 方法没有这些功能。Callable 能够返回装载有计算结果的 Future 对象。svg

咱们经过对比两个接口获得这样的结论:函数

  • Callable 接口下的方法是 call(),Runnable 接口的方法是 run();
  • Callable 的任务执行后可返回值,而 Runnable 的任务是不能返回值的;
  • call() 方法能够抛出异常,run()方法不能够的;
  • 运行 Callable 任务能够拿到一个 Future 对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。经过 Future 对象能够了解任务执行状况,可取消任务的执行,还可获取执行结果;

然而…Thread类只支持Runnable接口,由此引入FutureTask的概念。 oop


FutureTask

FutureTask 实现了 Runnable 和 Future,因此兼顾二者优势,既能够在 Thread 中使用,又能够在 ExecutorService 中使用。

public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;

    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

使用 FutureTask 的好处是 FutureTask 是为了弥补 Thread 的不足而设计的,它可让程序员准确地知道线程何时执行完成并得到到线程执行完成后返回的结果。FutureTask 是一种能够取消的异步的计算任务,它的计算是经过 Callable 实现的,它等价于能够携带结果的 Runnable,而且有三个状态:等待、运行和完成。完成包括全部计算以任意的方式结束,包括正常结束、取消和异常。

除了以上这些,在Android中充当线程的角色还有AsyncTask、HandlerThread、IntentService。它们本质上都是由Handler+Thread来构成的,不过不一样的设计让它们能够在不一样的场合发挥更好的做用。咱们来简单地说一下它们各自的特色:

AsyncTask,它封装了线程池和Handler,主要为咱们在子线程中更新UI提供便利。
HandlerThread,它是个具备消息队列的线程,能够方便咱们在子线程中处理不一样的事务。
IntentService,咱们能够将它看作为HandlerThread的升级版,它是服务,优先级更高。

下面我来经过源码,用法等来认清这些线程。


AsyncTask

AsyncTask是一个轻量级的异步任务类,它能够在线程池中执行后台任务,而后把执行的进度和结果传递给主线程而且在主线程中更新UI。

1. 用法

AsyncTask是一个抽象泛型类,声明:public abstract class AsyncTask<Params, Progress, Result>;
而且提供了4个核心方法。

  • 参数1,Params,异步任务的入参;
  • 参数2,Progress,执行任务的进度;
  • 参数3,Result,后台任务执行的结果;
  • 方法1, onPreExecute(),在主线程中执行,任务开启前的准备工做;
  • 方法2,doInbackground(Params…params),开启子线程执行后台任务;
  • 方法3,onProgressUpdate(Progress values),在主线程中执行,更新UI进度;
  • 方法4,onPostExecute(Result result),在主线程中执行,异步任务执行完成后执行,它的参数是doInbackground()的返回值。

从上面咱们能够清晰地了解到AsyncTask的具体用法,可是为何它是如此表现呢?或者说,为何它是这样而非其它样,那么,咱们就来看看源码吧。

2. 源码分析

咱们知道开启AsyncTask异步任务是经过new MyAsyncTask().execute()开启的,咱们就以此为入口开始分析。

2.1 execute()方法

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}

分析:从源码中,能够看到execute()方法调用了executeOnExecutor()方法。

2.2 executeOnExecutor()方法

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
        Params... params) {
    if (mStatus != Status.PENDING) {
        switch (mStatus) {
            case RUNNING:
                throw new IllegalStateException("Cannot execute task:"
                        + " the task is already running.");
            case FINISHED:
                throw new IllegalStateException("Cannot execute task:"
                        + " the task has already been executed "
                        + "(a task can be executed only once)");
        }
    }
    mStatus = Status.RUNNING;
    onPreExecute();
    mWorker.mParams = params;
    exec.execute(mFuture);
    return this;
}

分析:mStatus表明了当前异步任务的运行状态,咱们能够看出AsyncTask是一次性的,即不能重复调用execute()来开启异步任务。当该任务第一次启动时,状态设置为RUNNING,而且调用onPreExecute()级上文中提到了核心方法1。毫无疑问,此方法是protected void onPreExecute() {} 是须要咱们来实现的。

而后咱们将参数1Params给了mWorker,

咱们从2.1能够看到2.2中方法的参数exec就是sDefaultExecutor。

private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    Runnable mActive;

    public synchronized void execute(final Runnable r) {
        mTasks.offer(new Runnable() {
            public void run() {
                try {
                    r.run();
                } finally {
                    scheduleNext();
                }
            }
        });
        if (mActive == null) {
            scheduleNext();
        }
    }

    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) {
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

咱们能够看到它用来实现AsyncTask的排队执行,即AsyncTask是串行而非并发执行的。源码的大体执行过程咱们会在下面给出流程图。

2.3 mWork的call()方法

mWorker = new WorkerRunnable<Params, Result>() {
    public Result call() throws Exception {
        mTaskInvoked.set(true);     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);            
        //noinspection unchecked
        Result result = doInBackground(mParams);
        Binder.flushPendingCommands();
        return postResult(result);
    }
};

咱们能够看到此时开始执行AsyncTask的核心方法2,我固然也是须要咱们本身实现的。

2.4 sHandler

private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}

咱们在2.3中能够看到最后执行到了postResult()方法,此方法就是利用一个静态sHandler变量将消息发送出去而且交由主线程处理,这样一来就实现了子线程和主线程的切换问题。不只仅是此处由sHandler处理,当在子线程中执行doInbackground()时,若是咱们须要更新进度即调用核心方法3也须要利用sHandler发送消息给主线程处理。

2.5 finish

private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}

从2.4中看到,发送sHandler的MESSAGE_POST_RESULT消息,执行了finish()方法。该方法很简单,若是正常结束调用了核心方法4结束整个异步任务。若是在异步任务执行的过程当中被取消了那么调用onCancelled()方法。

流程图分析以下:

这里写图片描述

3. 基本使用

咱们从源码分析中,能够看到AsyncTask异步任务在内部是串行执行的,咱们为了提升让异步任务的执行效率,在Android3.0以后提供了,executeOnExecutor()方法。

来个通常的写法吧:

private class MyAsyncTask extends AsyncTask{

    private String mName;

    public MyAsyncTask(String name){
        mName = name;
    }

    @TargetApi(Build.VERSION_CODES.N)
    @Override
    protected Object doInBackground(Object[] params) {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Log.i(mName, dateFormat.format(new Date(System.currentTimeMillis())));
        return null;
    }

}

咱们能够经过这样的调用来验证其是串行仍是并发的。

(new MyAsyncTask("MyAsyncTask01")).execute();
(new MyAsyncTask("MyAsyncTask02")).execute();
(new MyAsyncTask("MyAsyncTask03")).execute();

能够看到这三个异步任务的执行时间是

I/MyAsyncTask01: 2017-05-26 20:35:29
I/MyAsyncTask02: 2017-05-26 20:35:32
I/MyAsyncTask03: 2017-05-26 20:35:35

由此可知它的执行是串行的,若是须要并发执行,调用executeOnExecutor()便可。

(new MyAsyncTask("MyAsyncTask01")).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");
(new MyAsyncTask("MyAsyncTask02")).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");
(new MyAsyncTask("MyAsyncTask03")).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");

看时间是这样的:

I/MyAsyncTask02: 2017-05-26 20:38:52
I/MyAsyncTask03: 2017-05-26 20:38:52
I/MyAsyncTask01: 2017-05-26 20:38:52

HandlerThread

HandlerThread继承了Thread,咱们都知道若是须要在线程中建立一个可接收消息的Handler,可参考个人另外一篇文章Android消息机制-Handler。因此HandlerThread其实是一个容许Handler的特殊线程。

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

普通线程在run()方法中执行耗时操做,而HandlerThread在run()方法建立了一个消息队列不停地轮询消息,咱们能够经过Handler发送消息来告诉线程该执行什么操做。

它在Android中是个颇有用的类,它常见的使用场景实在IntentService中。当咱们再也不须要HandlerThread时,咱们经过调用quit/Safely方法来结束线程的轮询并结束该线程。


IntentService

1. 概述

IntentService是一个继承Service的抽象类,因此咱们必须实现它的子类再去使用。

在说到HandlerThread时咱们提到,HandlerThread的使用场景是在IntentService上,咱们能够这样来理解IntentService,它是一个实现了HandlerThread的Service。

那么为何要这样设计呢?这样设计的好处是Service的优先级比较高,咱们能够利用这个特性来保证后台服务的优先正常执行,甚至咱们还能够为Service开辟一个新的进程。

2. 源码分析

咱们先来看看onCreate()函数:

@Override
public void onCreate() {
    super.onCreate();
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
}

咱们能够看到建立Service时,实现了一个HandlerThread的实例开启了一个线程,并在线程内部进行消息轮询,又建立了一个Handler来收发Looper的消息。

咱们每启动一次服务时,不会开启新的服务,只是会调用onStartCommand()函数,咱们又看到该函数调用了onStart()方法。

@Override
public void onStart(@Nullable Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    mServiceHandler.sendMessage(msg);
}

在该方法中咱们看到,这里用来接收Context传递的参数,经过Handler发送出去,而后再HandlerThread的线程上接收消息而且处理。

private final class ServiceHandler extends Handler {
    public ServiceHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        onHandleIntent((Intent)msg.obj);
        stopSelf(msg.arg1);
    }
}

咱们能够看到onHandleIntent()方法是咱们须要接收消息处理的。

流程以下

这里写图片描述


初识线程池

咱们在上面的篇幅中,讲到了线程的概念以及一些扩展线程。那么咱们考虑一个问题,我若是须要同时作不少事情,是否是给每个事件都开启一个线程呢?那若是个人事件无限多呢?频繁地建立/销毁线程,CPU该吃不消了吧。因此,这时候线程池的概念就来了。咱们举个例子来阐述一下线程池大体工做原理。

好比,有个老板戚总开了个饭店,每到中午就有不少人点外卖,一开始戚总招了10我的送外卖,然而因为午餐高峰期可能同时须要派送50份外卖,那如何保证高效地运行呢?

戚总想着那再招40个员工送?我去,那我这店岂不是要赔死,人员工资这么高,而且大部分时候也只须要同时派送几份外卖而已,招这么多人干瞪眼啊,是啊。但我还得保证高峰期送餐效率,咋办呢?

通过一番思想斗争,戚总想通了,我也不可能作到完美,尽可能高效就好了,那正常时间通常只须要同时送四五家外卖,那我就招5个员工做为正式员工(核心线程),再招若干兼职(非核心线程)在用餐高峰时缓解一下送餐压力便可。

那么,人员分配方案出来了,当正式员工(核心线程)空闲时有单进来理所应当让他们派送,若是正式员工忙不过了,就让兼职人员(非核心线程)送,按单提成唄。

好吧,啰嗦这么多,这就是线程池的概念原理吧。

咱们来总结一下优势吧。

  • 重用线程池中的线程,避免频繁地建立和销毁线程带来的性能消耗;
  • 有效控制线程的最大并发数量,防止线程过大致使抢占资源形成系统阻塞;
  • 能够对线程进行必定地管理。

ThreadPoolExecutor

ExecutorService是最初的线程池接口,ThreadPoolExecutor类是对线程池的具体实现,它经过构造方法来配置线程池的参数,咱们来分析一下它经常使用的构造函数吧。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

参数解释:

  • corePoolSize,线程池中核心线程的数量,默认状况下,即便核心线程没有任务在执行它也存在的,咱们固定必定数量的核心线程且它一直存活这样就避免了通常状况下CPU建立和销毁线程带来的开销。咱们若是将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程就会有超时策略,这个时间由keepAliveTime来设定,即keepAliveTime时间内若是核心线程没有回应则该线程就会被终止。allowCoreThreadTimeOut默认为false,核心线程没有超时时间。
  • maximumPoolSize,线程池中的最大线程数,当任务数量超过最大线程数时其它任务可能就会被阻塞。最大线程数=核心线程+非核心线程。非核心线程只有当核心线程不够用且线程池有空余时才会被建立,执行完任务后非核心线程会被销毁。
  • keepAliveTime,非核心线程的超时时长,当执行时间超过这个时间时,非核心线程就会被回收。当allowCoreThreadTimeOut设置为true时,此属性也做用在核心线程上。
  • unit,枚举时间单位,TimeUnit。
  • workQueue,线程池中的任务队列,咱们提交给线程池的runnable会被存储在这个对象上。

线程池的分配遵循这样的规则:

  • 当线程池中的核心线程数量未达到最大线程数时,启动一个核心线程去执行任务;
  • 若是线程池中的核心线程数量达到最大线程数时,那么任务会被插入到任务队列中排队等待执行;
  • 若是在上一步骤中任务队列已满可是线程池中线程数量未达到限定线程总数,那么启动一个非核心线程来处理任务;
  • 若是上一步骤中线程数量达到了限定线程总量,那么线程池则拒绝执行该任务,且ThreadPoolExecutor会调用RejectedtionHandler的rejectedExecution方法来通知调用者。

线程池的分类

咱们来介绍一下不一样特性的线程池,它们都直接或者间接经过ThreadPoolExecutor来实现本身的功能。它们分别是:

  • FixedThreadPool
  • CachedThreadPool
  • ScheduledThreadPool
  • SingleThreadExecutor

1. FixedThreadPool

经过Executors的newFixedThreadPool()方法建立,它是个线程数量固定的线程池,该线程池的线程所有为核心线程,它们没有超时机制且排队任务队列无限制,由于全都是核心线程,因此响应较快,且不用担忧线程会被回收。

public static ExecutorService newFixedThreadPool(int nThreads){
    return new ThreadPoolExecutor(
        nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>() 
        );
}
ExecutorService mExecutor = Executors.newFixedThreadPool(5);

参数nThreads,就是咱们固定的核心线程数量。

2. CachedThreadPool

经过Executors的newCachedThreadPool()方法来建立,它是一个数量无限多的线程池,它全部的线程都是非核心线程,当有新任务来时若是没有空闲的线程则直接建立新的线程不会去排队而直接执行,而且超时时间都是60s,因此此线程池适合执行大量耗时小的任务。因为设置了超时时间为60s,因此当线程空闲必定时间时就会被系统回收,因此理论上该线程池不会有占用系统资源的无用线程。

public static ExecutorService new CachedThreadPool(){
    return new ThreadPoolExecutor(
        0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>()
    );
}

3. ScheduledThreadPool

经过Executors的newScheduledThreadPool()方法来建立,ScheduledThreadPool线程池像是上两种的合体,它有数量固定的核心线程,且有数量无限多的非核心线程,可是它的非核心线程超时时间是0s,因此非核心线程一旦空闲立马就会被回收。这类线程池适合用于执行定时任务和固定周期的重复任务。

public static ScheduledThreadPool newScheduledThreadPool(int corePoolSize){
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize){
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

参数corePoolSize是核心线程数量。

4. SingleThreadExecutor

经过Executors的newSingleThreadExecutor()方法来建立,它内部只有一个核心线程,它确保全部任务进来都要排队按顺序执行。它的意义在于,统一全部的外界任务到同一线程中,让调用者能够忽略线程同步问题。

public static ExecutorService newSingleThreadExecutor(){
    return new FinalizableDelegatedExecutorService(
        new ThreadPoolExecutor(
        1, 1, 0L, TimeUnit.MILLISECONDS, 
        new LinkedBlockingQueue<Runnable>()));
}

线程池通常用法

  • shutDown(),关闭线程池,须要执行完已提交的任务;
  • shutDownNow(),关闭线程池,并尝试结束已提交的任务;
  • allowCoreThreadTimeOut(boolen),容许核心线程闲置超时回收;
  • execute(),提交任务无返回值;
  • submit(),提交任务有返回值;

自定义线程池

ExecutorService mExecutor = Executors.newFixedThreadPool(5);

execute()方法

接收一个Runnable对象做为参数,异步执行。

Runnable myRunnable = new Runnable() {
    @Override
    public void run() {
        Log.i("myRunnable", "run");
    }
};
mExecutor.execute(myRunnable);