重温Android中的线程、线程池源码

本文深入探讨了Android中线程和线程池的使用,包括AsyncTask、HandlerThread、IntentService和线程池的工作原理。通过源码分析,揭示了AsyncTask的执行流程、限制和最佳实践,解释了为何在不同场景下选择HandlerThread、IntentService的原因,以及Executor和线程池的分类与配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本篇博文涉及到的一些Android中常用的线程相关知识,源码也比较简单,作为重温笔记。 

Android中的线程、线程池,,如下图所示:

 AsyncTask

                  

上述方法的执行顺序是:1 -> 2 -> 3 -> 4

对于AsyncTask有如下几点可以从源码中得到验证

  • AsyncTask的对象必须在主线程中创建(非必须,后面会讲)
  • execute方法必须在UI线程调用(非必须,后面会讲)
  • 不要在程序中直接调用onPreExecute、onPostExecute、doInBackground和onProgressUpdate方法
  • 一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行时异常
  • AsyncTask默认是串行执行任务的,但是我们仍然可以通过AsyncTask的executeOnExecutor方法来并行地执行任务
  • AsyncTask 调用 cancel() 任务是否立即停止执行?onPostExecute() 还会被调用吗?onCancelled() 什么时候被调用?

AsyncTask的一般用法如下:

// 除了doInBackground是必须重写的方法,其他的都是可选的实现方法
public class MyAsyncTask extends AsyncTask<String, Integer, Integer> {

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected Integer doInBackground(String... strings) {
        return null;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
    }

    @Override
    protected void onPostExecute(Integer integer) {
        super.onPostExecute(integer);
    }

    @Override
    protected void onCancelled(Integer integer) {
        super.onCancelled(integer);
    }
}
===============================================================
        MyAsyncTask myAsyncTask = new MyAsyncTask();
        myAsyncTask.execute();// 同步执行

        myAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");// 并发执行
        myAsyncTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, "");// 同步执行

源码解读:

如下两点是非必须的:

  • AsyncTask的对象必须在主线程中创建
  • execute方法必须在UI线程调用

之前看过一些网上的资料还有一些书籍,都说如上的1,2条。但是并未详细的解释为什么要这样。阅读了AsyncTask的源码你会发现,强制要求上面两点是错误的。为什么呢?看源码

/**
 * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
 */
public AsyncTask() {
  this((Looper) null);
}


   /**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
     *
     * @hide  // 1
     */
public AsyncTask(@Nullable Handler handler) {
    this(handler != null ? handler.getLooper() : null);
}
  
   /**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
     *
     * @hide // 2
     */
public AsyncTask(@Nullable Looper callbackLooper) {
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);

        ......
    }

如上源码中的1和2处,@hide意味着这两个构造方法是系统使用的而不是给开发者准备的,开发者只能使用第一个无参的构造方法。无参的构造方法会调用上述源码最后一个构造方法,保证了我们可以拿到主线程的Looper。

我们知道UI线程的Looper是在ActivityThread的main方法中创建的

------ ActivityThread.java

   public static void main(String[] args) {
        ......

        Looper.prepareMainLooper();

        ......
        Looper.loop();

        .....
    }

而Looper的prepareMainLooper方法是单例模式的

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

    /**
     * Returns the application's main looper, which lives in the main thread of the application.
     */
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

所以,即便我们在非UI线程创建AsyncTask对象,AsyncTask依然具备切换到UI线程进行UI更新的操作的能力。所以强制要求第一点(AsyncTask的对象必须在主线程中创建)是错误的。

下面看下第二点(execute方法必须在UI线程调用)为什么也是非必须的?

上面说了,无论在UI还是工作线程,AsyncTask都具备切换到UI线程进行UI更新的能力,这可以保证onPostExecute方法一定是运行的UI线程中的(执行onPostExecute,此时已经切换到了UI线程),

    private static class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);// 执行onPostExecute方法
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

但是onPreExecute是没有这种保证的,因为此时在工作者线程调用execute方法的话,onPreExecute是运行在非UI线程的。

   public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        .......

        onPreExecute();// 在哪个线程调用execute方法,则运行在哪个线程中

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }

调用AsyncTask的execute方法,会执行上述代码。当然,如果在onPreExecute方法中不做更新UI的操作或者通过其他方法比如runOnUiThread等方法切换到UI线程进行UI的更新也就不会产生问题了。

既然这样,那为啥AsyncTask的源码注释要求我们要在UI线程创建AsyncTask对象以及执行execute方法呢?

为了版本的兼容。新版代码虽然没这个问题了,但我们写代码时无法预知用户手机的版本,为兼容旧版本,故新版源码构造方法及其他相关方法的注释仍写着必须在UI线程创建。

  • 不要在程序中直接调用onPreExecute、onPostExecute、doInBackground和onProgressUpdate方法
  • 这个很容易理解,他们都是AsyncTask内部的protected方法,各个方法之间有先后的执行顺序的,强制手动调用这些方法可能会出现一些问题
  • 一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行时异常
      @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

  @MainThread
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                //  1
                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;
    }

调用AsyncTask的execute方法,会执行executeOnExecutor方法,上述1处的源码我们可以发现,每个AsyncTask对象都是有状态的,有如下两个结论:

  1. 处于运行状态的AsyncTask不能再次执行
  2. 一个AsyncTask只能被执行一次

这也就解释了本条要得出的结论。

  • AsyncTask默认是串行执行任务的,但是我们仍然可以通过AsyncTask的executeOnExecutor方法来并行地执行任务
        public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    
        private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
    
        @MainThread
        public final AsyncTask<Params, Progress, Result> execute(Params... params) {
            return executeOnExecutor(sDefaultExecutor, params);
        }
    
    // 借助ArrayDeque和线程池,顺序执行的Executor 
        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);
                }
            }
        }

    可以看到,默认情况下是使用SerialExecutor 来执行任务的,而SerialExecutor 借助ArrayDeque和线程池,保证了任务是按照顺序一个一个执行的。

那是不是AsyncTask是不是只支持同步执行任务呢?并不是,取决于我们使用的线程池。

myAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");// 并发执行
myAsyncTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, "");// 同步执行

Android 1.6之前,AsyncTask是串行执行; Android 1.6 - 3.0,AsyncTask是并行执行的;Android 3.0 - 及以后版本,默认是串行执行的。

  • AsyncTask 调用 cancel() 任务是否立即停止执行?onPostExecute() 还会被调用吗?onCancelled() 什么时候被调用?

任务不会立即停止的,我们调用 cancel 的时候,只是将 AsyncTask 设置为 canceled(可取消)状态,我们从以下代码可以看出,AsyncTask 设置为已取消的状态,那么之后 onProgressUpdate 和 onPostExecute 都不会被调用,而是调用了 onCancelled() 方法。onCancelled() 方法是在异步任务结束的时候才调用的。时机是和 onPostExecute 方法一样的,只是这两个方法是互斥的,不能同时出现。

@WorkerThread
    protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }
    
// 线程执行完之后才会被调用
private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

HandlerThread

HandlerThread的原理比较简单,一句话就是:具有Looper的Thread。

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

 要想使用HandlerThread只需要调用其start方法即可,这样就可以拿到HandlerThread的Looper,进而具备切换到HandlerThread工作线程执行任务的能力。

IntentService

IntentService的代码量也很少,其原理也比较容易理解,简单来说具备了三个特点:

  • 相比普通Thread,IntentService继承Service具有较高的优先级,不容易被杀死
  • 相比普通Service,HandlerThread可以执行耗时任务
  • IntentService执行完任务,自动结束

第一点很容易理解,不再详述。

  • 相比普通Service,HandlerThread可以执行耗时任务
  @Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

 在IntentService的onCreate方法中,其内部的ServiceHandler的Looper来源于HandlerThread,这样IntentService就具备了在单独的线程执行耗时任务的能力。具体执行流程如下:

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

可以看到,IntentService在onStart方法接收到Intent之后,会向ServiceHandler发送一个消息,这个消息会携带启动Id和Intent。

    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);
        }
    }

 ServiceHandler接收到消息之后,会在handleMessage中处理消息。主要工作如下:

  • 回调onHandleIntent的用户自定义处理消息逻辑;
  • 结束Service - 第三点在这里得到验证

注意:stopSelf(id)并不是立即停止Service,而是等到所有的消息都处理完了再停止Service。比如多次启动同一个IntentService。

Intent intent = new Intent(this,MyIntentService.class);

intent.putExtra("task_action","action1");
startService(intent);

intent.putExtra("task_action","action2");
startService(intent);

intent.putExtra("task_action","action3");
startService(intent);

Execuotr

Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor。ThreadPoolExecutor提供了一些列参数来配置线程池,通过不同的参数可以创建不同的线程池,Android的线程池主要分为四类,这四类线程池可以通过Executors所提供的工厂方法来得到。由于Android中的线程池都是直接或者间接通过配置ThreadPoolExecutor来实现的,因此先学习下ThreadPoolExecutor。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }
  • corePoolSize

线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即使它们处于闲置状态。如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来时会有超时策略,这个时间间隔由keepAliveTime所指定,当等待时间超过keepAliveTime所指定的时长时,核心线程就会被中止。

  • maximumPoolSize

线程池所能容纳的最大线程数,当活动线程数达到这个数值后,后续的新任务就会被阻塞。

  • keepAliveTime

 非核心线程闲置时的超时时长,超过这个时长,非核心线程将被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,keepAliveTime同样是作用于核心线程。

  • workQueue

线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中

  • threadFactory

线程工厂,为线程池提供创建新线程的功能。

任务提交的大概逻辑如下:

When a new task is submitted in method {@link #execute(Runnable)},and fewer than corePoolSize threads are running, a new thread is created to handle the request, even if other worker threads are idle.  If there are more than corePoolSize but less than maximumPoolSize threads running, a new thread will be created only if the queue is full.
The use of this queue interacts with pool sizing:
*
* If fewer than corePoolSize threads are running, the Executor
* always prefers adding a new thread
* rather than queuing.
*
* If corePoolSize or more threads are running, the Executor
* always prefers queuing a request rather than adding a new
* thread.
*
* If a request cannot be queued, a new thread is created unless
* this would exceed maximumPoolSize, in which case, the task will be
* rejected.

1)当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程;

2)当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行;

3)当workQueue已满,且maximumPoolSize > corePoolSize时,新提交任务会创建新线程执行任务;

4)当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理;

5)当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程;

线程池的分类

通过Executors,可以生成四类典型的线程池

  • FixedThreadPool

线程数量固定,且都为核心线程。

当线程处于空闲状态也不会被回收,除非线程池被关闭了。当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来。

- 只有核心线程且没有超时机制

  • CachedThreadPool

线程数量不固定的线程池,且均为非核心线程,最大线程数为Integer.MAX_VALUE。

当线程池中的线程都处于活动状态,线程池会创建新的线程来处理新的任务,否则就会利用空闲的线程来处理新任务。线程池中的空闲线程都有超时机制,这个超时的时长为60s,超过60s闲置线程就会被回收。

与FixedThreadPool不同的是,CachedThreadPool的任务队列其实相当于一个空集合,这将导致任何任务都会被立即执行。

CachedThreadPool适合执行大量的耗时较少的任务,当整个线程池都处于空闲状态,线程池中的线程都会超时而被停止,这个时候CachedThreadPool之中实际上是没有任何线程的,它几乎不占用任何系统资源。

- 只有非核心线程,有超时机制,超时时间为60s.

  • ScheduledThreadPool

核心线程的数量固定,非核心线程的数量没有限制,并且当非核心线程闲置时会被立即回收。

  • SingleThreadPool

只有一个核心线程,确保所有的任务都在一个线程中按顺序执行,任务之间无同步问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值