《Android开发艺术探索》第11章-Android 的线程和线程池读书笔记
目录
- 1. 前言
- 2. 正文
-
- 2.1 主线程和子线程
-
- 2.1.1 在 Android 中,可以扮演线程角色的类有哪些?
- 2.1.2 线程池的好处是什么?
- 2.1.3 Android 中主线程和子线程的职责分别是什么?
- 2.2 Android 中的线程形态
-
- 2.2.1 AsyncTask 实际开发中如何使用?
- 2.2.2 AsyncTask 的两个操作方法,三个泛型参数和五个核心方法分别是什么?
- 2.2.3 AsyncTask 在使用过程中有哪些限制?
- 2.2.4 AsyncTask 的类加载是在哪里完成的?会完成哪些工作?
- 2.2.5 AsyncTask 的工作原理是什么?
- 2.2.6 AsyncTask 内存泄漏是怎么产生的?如何解决?
- 2.2.7 如何让 AsyncTask 并行执行任务呢?
- 2.2.8 HandlerThread 的工作过程是什么?
- 2.2.9 IntentService 和 Service 的区别是什么?
- 2.2.10 IntentService 的工作过程是什么?
- 2.2.11 IntentService 是如何保证多个异步任务的顺序执行的?
- 2.2.12 从源码上找出 Service 的 stopSelf() 和 stopSelf(int startId) 的区别在哪里?
- 2.3 Android 中的线程池
-
- 2.3.1 ThreadPoolExecutor 的构造方法中各个参数的含义是什么?
- 2.3.2 ThreadPoolExecutor 执行任务的规则是什么?
- 2.3.3 ThreadPoolExecutor 的 UML 类图是什么?
- 2.3.4 Executors 里的线程池有哪些?这些线程池有什么特点?
- 3. 最后
- 参考
1. 前言
本文主要对 Android 的线程和线程池部分进行总结。
2. 正文
2.1 主线程和子线程
2.1.1 在 Android 中,可以扮演线程角色的类有哪些?
Thread
,AsyncTask
,HandlerThread
和 IntentService
。在 Jetpack 中,新增加的 WorkManager
。
类 | 描述 | 用途 |
---|---|---|
AysncTask |
封装了线程池和 Handler |
为了方便开发者处理异步任务,并在 UI 线程更新数据。 |
HandlerThread |
是一种具有消息循环的线程,在它的内部使用了 Handler |
方便构建具有消息循环的线程。 |
IntentService |
是一个服务,内部采用 HandlerThread 来执行异步任务,当任务执行完毕后 IntentService 会自动退出。 |
可以方便地执行异步任务,结合了线程(异步)和服务(不容易被系统杀死)。 |
2.1.2 线程池的好处是什么?
线程的特点:
-
线程是一种受限的系统资源,即线程不可能无限制地产生;
-
线程的创建和销毁都会有相应的开销;
-
线程的调度是系统通过时间片轮转的方式来进行,这也有一定的开销,线程很多时,开销更大。
线程池可以缓存一定数量的线程,避免因为频繁创建和销毁线程带来的系统开销。
- 重用线程池中的线程,避免因为线程的创建和销毁所带来的的性能开销;
- 能够有效地控制线程池的最大并发数,避免大量的线程间因相互抢占系统资源而导致的阻塞现象;
- 能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行。
2.1.3 Android 中主线程和子线程的职责分别是什么?
主线程主要负责界面交互的逻辑,在任何时候必须保证有较高的响应速度,否则界面会有卡顿的感觉,主线程不能处理耗时任务;
子线程也叫工作线程,除了主线程以外的线程都是子线程,主要负责处理耗时操作。
如果在主线程执行了耗时操作,如进行网络访问,从 Android3.0 开始网络访问会失败并抛出 NetworkOnMainThreadException
异常;进行I/O操作或者其他耗时操作,主线程会因超过响应时间限制从而出现 ANR 现象。
2.2 Android 中的线程形态
2.2.1 AsyncTask 实际开发中如何使用?
public class AsyncTaskActivity extends Activity { private static final String TAG = AsyncTaskActivity.class.getSimpleName(); private static final String url = "https://github.com/jhwsx/android-art-research/raw/" + "f6257e9f1e46848400f7ff2635991fd5a850d4f8/chapter_11/app-debug.apk"; TextView mTvProgress; TextView mTvLength; private DownloadTask mDownloadTask; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_asynctask); Button btnStartDownload = (Button) findViewById(R.id.btn_start_download); Button btnCancelDownload = (Button) findViewById(R.id.btn_cancel_download); mTvProgress = (TextView) findViewById(R.id.tv_download_progress); mTvLength = (TextView) findViewById(R.id.tv_download_length); btnStartDownload.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mDownloadTask = new DownloadTask(AsyncTaskActivity.this); mDownloadTask.execute(url); } }); btnCancelDownload.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mDownloadTask.cancel(false); } }); } public static class DownloadTask extends AsyncTask<String, Integer, Long> { private WeakReference<AsyncTaskActivity> weakReference; private ProgressDialog mProgressDialog; public DownloadTask(AsyncTaskActivity activity) { weakReference = new WeakReference<>(activity); } @Override protected void onPreExecute() { super.onPreExecute(); mProgressDialog = new ProgressDialog(weakReference.get()); mProgressDialog.setTitle("下载"); mProgressDialog.setMessage("获取数据中..."); mProgressDialog.show(); Log.d(TAG, "onPreExecute: threadName=" + Thread.currentThread().getName()); } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); if (mProgressDialog != null && mProgressDialog.isShowing()) { mProgressDialog.dismiss(); mProgressDialog = null; } Log.d(TAG, "onProgressUpdate: progress=" + values[0] + ", threadName=" + Thread.currentThread().getName()); AsyncTaskActivity asyncTaskActivity = weakReference.get(); if (asyncTaskActivity != null) { asyncTaskActivity.mTvProgress.setText("下载进度: " + values[0] + "%"); } } @Override protected Long doInBackground(String... urls) { Log.d(TAG, "doInBackground: threadName=" + Thread.currentThread().getName()); HttpURLConnection connection = null; InputStream inputStream = null; OutputStream outputStream = null; long contentLength = -1; try { URL url = new URL(urls[0]); connection = (HttpURLConnection) url.openConnection(); connection.setConnectTimeout(30 * 1000); connection.setReadTimeout(30 * 1000); connection.connect(); if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { return -1L; } contentLength = connection.getContentLength(); inputStream = connection.getInputStream(); outputStream = new FileOutputStream(new File(weakReference.get().getExternalFilesDir(null), "download.apk")); byte[] buffer = new byte[4 * 1024]; int total = 0; int length; while ((length = inputStream.read(buffer)) != -1) { total += length; final int progress = (int) (total * 100f / contentLength); if (isCancelled()) { break; } publishProgress(progress); outputStream.write(buffer, 0, length); } } catch (Exception e) { e.printStackTrace(); } finally { CloseUtils.closeIOQuietly(inputStream, outputStream); if (connection != null) { connection.disconnect(); } } return contentLength; } @Override protected void onPostExecute(Long aLong) { super.onPostExecute(aLong); Log.d(TAG, "onPostExecute: threadName=" + Thread.currentThread().getName()); AsyncTaskActivity asyncTaskActivity = weakReference.get(); if (asyncTaskActivity != null) { asyncTaskActivity.mTvLength.setText("下载字节数: " + aLong); asyncTaskActivity.mTvLength.setVisibility(View.VISIBLE); } } @Override protected void onCancelled() { super.onCancelled(); Log.d(TAG, "onCancelled: " + Thread.currentThread().getName()); AsyncTaskActivity asyncTaskActivity = weakReference.get(); if (asyncTaskActivity != null) { asyncTaskActivity.mTvLength.setVisibility(View.VISIBLE); asyncTaskActivity.mTvLength.setText("任务已取消"); } } }}
点击开始下载按钮,打印日志如下:
D/AsyncTaskActivity: onPreExecute: threadName=mainD/AsyncTaskActivity: doInBackground: threadName=AsyncTask #1D/AsyncTaskActivity: onProgressUpdate: progress=0, threadName=mainD/AsyncTaskActivity: onProgressUpdate: progress=1, threadName=mainD/AsyncTaskActivity: onProgressUpdate: progress=2, threadName=main...D/AsyncTaskActivity: onProgressUpdate: progress=98, threadName=mainD/AsyncTaskActivity: onProgressUpdate: progress=100, threadName=mainD/AsyncTaskActivity: onPostExecute: threadName=main
2.2.2 AsyncTask 的两个操作方法,三个泛型参数和五个核心方法分别是什么?
AsyncTask
是一个抽象的泛型类,它提供了 Params
、Progress
和 Result
这三个泛型参数。其中 Params
表示异步任务的输入参数的类型,如下载链接等,Progress
表示异步任务的执行进度的类型,比如进度的百分数,Result
表示异步任务的返回结果的类型,如下载文件的长度。如果确实不需要某个具体的参数,可以传入 Void
来代替。
五个核心方法:
方法 | 执行线程 | 作用 |
---|---|---|
onPreExecute() |
主线程 | 在异步任务执行之前,回调此方法,一般可以做一些准备工作。 |
Result doInBackground(Params... params) |
线程池 | 用于执行异步任务,params 参数表示异步任务的输入参数,返回值表示要返回 onPostExecute 方法的结果。另外,在这个方法里,通过 publishProgress(Progress... values) 方法来更新异步任务的进度,最终会调用 onProgressUpdate 方法。 |
onProgressUpdate(Progress... values) |
主线程 | 当异步任务的执行进度发生改变时会回调这个方法。 |
onPostExecute(Result result) |
主线程 | 在异步任务执行完毕后,会回调此方法,接收 doInBackground 的返回值。 |
onCancelled(Result result) |
主线程 | 当异步任务被取消时,会回调此方法,此时不会回调 onPostExecute 。 |
两个操作方法:
方法 | 调用线程 | 作用 |
---|---|---|
AsyncTask execute(Params... params) |
主线程 | 开始执行异步任务 |
boolean cancel(boolean mayInterruptIfRunning) |
没有限制 | 取消异步任务的执行 |
2.2.3 AsyncTask 在使用过程中有哪些限制?
AsyncTask
的类必须在主线程中加载,这主要是为了保证sHandler
的创建是在主线程,这一点 android22 之后的AsyncTask
已发生了变化;execute()
方法在主线程调用,这主要是为了保证onPreExecute
方法在主线程调用;- 不要在程序中直接调用
onPreExecute()
、onPostExecute()
、doInBackground
和onProgressUpdate
方法; - 一个
AsyncTask
对象只能调用一次execute
方法,多次调用会报运行时异常; - 在 Android1.6 之前,
AsyncTask
是串行执行任务的,Android1.6 至 Android2.3,AsyncTask
采用线程池里处理并行任务,Android3.0 开始,AsyncTask
采用一个线程来串行执行任务。
这些限制的原因会在本文的后面逐个进行说明的。
2.2.4 AsyncTask 的类加载是在哪里完成的?会完成哪些工作?
AsyncTask
的类加载在 Android4.1及以上已经被系统自动完成,具体是在 ActivityThread
的 main
方法中,调用 AsyncTask
的 init
方法来完成的。
public static void main(String[] args) { // 创建主消息循环的 Looper 对象 Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } // 初始化 AsyncTask AsyncTask.init(); // 开启主消息循环 Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited");}
我们知道,静态成员在加载类的时候会进行初始化,所以我们还应该关注 AsyncTask
有哪些静态成员被初始化了。
public abstract class AsyncTask<Params, Progress, Result> { private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int CORE_POOL_SIZE = CPU_COUNT + 1; private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; private static final int KEEP_ALIVE = 1; private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128); public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); private static final InternalHandler sHandler = new InternalHandler(); private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; public static void init() { sHandler.getLooper(); } private static class InternalHandler extends Handler { }}
可以看到,有两个线程池(THREAD_POOL_EXECUTOR
和 SERIAL_EXECUTOR
)和一个 Handler sHandler
被初始化了。
需要说明的是,sHander
这个对象是在主线程创建的,这样在工作线程中才能通过 sHander
将数据从工作线程切换到主线程中。
2.2.5 AsyncTask 的工作原理是什么?
AsyncTask 的构造
public abstract class AsyncTask<Params, Progress, Result> { private final WorkerRunnable<Params, Result> mWorker; private final FutureTask<Result> mFuture; public AsyncTask() { mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); return postResult(doInBackground(mParams)); } }; mFuture = new FutureTask<Result>(mWorker) { @Override protected void done() { try { postResultIfNotInvoked(get()); } catch (InterruptedException e) { android.util.Log.w(LOG_TAG, e); } catch (ExecutionException e) { throw new RuntimeException("", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); } } }; } private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> { Params[] mParams; }}
FutureTask
类在开发中见得不是很多,这里看一下对应的类图:
可以看到,FutureTask
实现了 RunnableFuture
接口,而 RunnableFuture
接口继承了 Runnable
接口,所以 FutureTask
也实现了 Runnable
接口,这样 FutureTask
对象就可以放在线程池中执行了。
当 FutureTask
封装的任务被执行时,线程池会调用它的 run
方法,而 run
方法内部会调用 Callable
对象的 call
方法。
在这里,mFuture
对象会被放入线程池中执行,线程池会调用它的 run
方法,run
方法内部会调用 mWorker
对象的 call
方法,在 call
方法执行异步任务。
这里绘制一下它们之间的包含关系图,方便理解:
异步任务的提交
调用 execute
方法开始执行任务,内部会调用 executeOnExecutor
方法:
public abstract class AsyncTask<Params, Progress, Result> {public final AsyncTask<Params, Progress, Result> execute(Params... params) {return executeOnExecutor(sDefaultExecutor, params);}public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,Params... params) { // 如果任务状态不是 PENDING 状态,都会抛出异常。if (mStatus != Status.PENDING) {switch (mStatus) {case RUNNING:throw new IllegalStateException("");case FINISHED:throw new IllegalStateException("");}} // 当前任务状态是 PENDING 状态,把任务状态更新为 RUNNING 状态。mStatus = Status.RUNNING; // 调用此方法,做执行任务前的一些准备工作。onPreExecute(); // 把参数数组赋值给 mWorker 的 成员变量 mParamsmWorker.mParams = params; // 把异步任务提交到线程池里面exec.execute(mFuture); // 返回 AsyncTask 对象,便于外部拿到它来取消,发布进度,判断是否取消等操作。return this;}public enum Status {/ * 任务还没有执行 */PENDING,/ * 任务正在运行 */RUNNING,/ * 任务结束了,包括执行完毕或者被取消 */FINISHED,}}
通过 mStatus
的控制,来保证一个 AsyncTask
对象只能调用一次 execute
方法,否则就抛出运行异常。
调用 execute
方法,实际上是把异步任务提交给默认的线程池来执行,这是一个串行的线程池。那么,这里是如何保证串行的呢?可以看下面的源码:
// 可以并行执行任务的线程池public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);// 一次执行一个任务,即串行执行任务的线程池// 需要注意的是 SERIAL_EXECUTOR 是进程唯一的。public static final Executor SERIAL_EXECUTOR = new SerialExecutor();private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;private static class SerialExecutor implements Executor { // 用来存放即将要执行的异步任务,因为 SERIAL_EXECUTOR 是进程唯一的,所以 mTask 也是进程唯一的。 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() { // 从队列的头部取出一个任务,赋值给 mActive,如果不为空,则放入线程池中执行。 if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } }}
因为 SERIAL_EXECUTOR
是进程唯一的,而默认情况下就是使用 SERIAL_EXECUTOR
作为线程池,所以一个进程中所有的 AsyncTask
都会在这个串行的线程池中排队执行。
SerialExecutor
类并不是在 AsyncTask
中才有的,实际上,它是来自 Java 的 Executor
文档里面的。它通过使用 ArrayDeque
这个数组双端队列把任务调度的并行方式改为了串行方式。
这算得是静态代理模式的应用了,Executor
是公共的接口,SerialExecutor
是一个代理类,ThreadPoolExecutor
是真正的实体,也就是被代理类。
需要说明的是,exec.execute(mFuture);
只是把异步任务提交给了线程池而已,并不意味着异步任务会马上执行。这个方法的注释可以证明这一点:Executes the given task sometime in the future.
异步任务的执行
前面我们知道异步任务的提交已经完成了,那么异步任务的执行是在哪里呢?
异步任务的执行当然是在线程池里面,具体来说式在 ThreadPoolExecutor
的 runWorker
去执行异步任务,内部会调用 mFutureTask
对象(它是一个 Runnable
对象)的 run
方法,run
方法内部会调用 mWorker
对象(它是一个 Callable
对象)的 call
方法,因为我们知道 call
方法最终会在线程池中执行。
private final AtomicBoolean mTaskInvoked = new AtomicBoolean();mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); return postResult(doInBackground(mParams)); }};protected abstract Result doInBackground(Params... params);
在 call
方法中,主要做了三件事情:
- 把
mTaskInvoked
这个原子的布尔型成员变量的值设置为true
; - 执行
AsyncTask
的doInBackground(Params... params)
,这个方法接收异步任务的输入参数(比如下载链接)并执行异步任务的代码(比如下载文件); - 把
doInBackground
方法的返回值(比如下载文件的大小)传递给postResult
方法。
异步任务的进度和结果
主线程需要获取异步任务的进度(如下载文件的百分比)和结果(如文件大小或者被取消了),以便在 UI 展示下载进度和下载结果。但是,异步任务是在线程池里面执行的,它的进度和结果目前都在子线程中。
那么,怎样把子线程的进度和结果数据切换到主线程呢?当然是要用到 Handler
了。
先看一下如何把子线程中异步任务的进度切换到主线程,需要在 doInBackground
方法中调用 publishProgress
方法
// sHandler 是 AsyncTask 类加载的时候初始化的,而 AsyncTask 类加载是在主线程,所以 sHandler 是在主线程创建的。private static final InternalHandler sHandler = new InternalHandler();protected final void publishProgress(Progress... values) { if (!isCancelled()) { sHandler.obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult<Progress>(this, values)).sendToTarget(); }}
可以看到,在 isCancelled()
方法不为 true
时,也就是说异步任务没有取消时,先把 AsyncTask
对象和进度信息封装在 AsyncTaskResult
对象里,再把 AsyncTaskResult
对象封装在 Message
对象里面,最后通过 sHandler
把 Message
对象发送到消息队列里面。
private static class AsyncTaskResult<Data> { final AsyncTask mTask; final Data[] mData; AsyncTaskResult(AsyncTask task, Data... data) { mTask = task; mData = data; }}
其实这里使用 AsyncTaskResult
这个名字来表示进度信息很不准确,觉得叫 AsyncTaskProgress
还是很贴切的,对吧?
接着我们去看一下接收消息的地方,自然是在 InternalHandler
类重写的 handleMessage
方法里面:
private static class InternalHandler extends Handler { @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) @Override public void handleMessage(Message msg) { // 这个方法是在主线程执行的。 AsyncTaskResult result = (AsyncTaskResult) msg.obj; switch (msg.what) { ... case MESSAGE_POST_PROGRESS: result.mTask.onProgressUpdate(result.mData); break; } }}
result.mTask
就是当前的 AsyncTask
对象,result.mData
就是要更新的进度值,它是一个数组。
result.mTask.onProgressUpdate(result.mData);
就是调用 AsyncTask
的 onProgressUpdate
方法,把进度值回调出去,这样就可以在重写的 AsyncTask
的 onProgressUpdate
方法里拿到进度值来更新页面上的进度值了。
异步任务的结果是通过 postResult
方法,来发送的:
private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); return result;}
在 AsyncTask
里面,会有两处调用 postResult
方法:
-
在异步任务代码执行完毕后,即
doInBackground
方法执行完毕后,调用postResult
方法来发送结果:mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked return postResult(doInBackground(mParams)); }};
-
在调用
AsyncTask
的cancel
方法后,最终会调用FutureTask
的done
方法:public final boolean cancel(boolean mayInterruptIfRunning) { // 把 mCancelled 的值设为 true,表示已经进行了取消操作 mCancelled.set(true); return mFuture.cancel(mayInterruptIfRunning);}mFuture = new FutureTask<Result>(mWorker) { @Override protected void done() { postResultIfNotInvoked(get()); }};private void postResultIfNotInvoked(Result result) { final boolean wasTaskInvoked = mTaskInvoked.get(); // 如果任务还没有被调用,就发送结果。 if (!wasTaskInvoked) { postResult(result); }}
接着看 postResult
方法,仍然是先把 AsyncTask
对象和结果信息封装在 AsyncTaskResult
对象里,再把 AsyncTaskResult
对象封装在 Message
对象里面,最后通过 sHandler
把 Message
对象发送到消息队列里面。
private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); return result;}
接收结果的地方是在 InternalHandler
的 handleMessage
方法里:
case MESSAGE_POST_RESULT: // 在主线程里调用 result.mTask.finish(result.mData[0]); break;
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED;}
可以看到 finish
方法里面,会先通过 isCancelled
判断有没有进行取消操作;如果已取消,就调用 onCancelled
方法,如果未取消,说明拿到了异步任务的结果,就调用 onPostExecute
方法;最后,把 mStatus
的值改为 FINISHED
。
从这里我们可以得出,对于一个 AsyncTask
对象来说,它的 onCancelled
方法和 onPostExecute
方法只会有一个会被调用;一个 AsyncTask
对象在执行完一次异步任务后,不能再次调用它的 execute
方法,这是因为它的 mStatus
为 FINISHED
而不是 PENDING
,再次调用 execute
方法会抛出运行异常。
2.2.6 AsyncTask 内存泄漏是怎么产生的?如何解决?
当在 Activity
中采用匿名类或者内部类的方式来创建 AsyncTask
时,AsyncTask
默认会持有外部类 Activity
的引用;如果在 Activity
关闭后,AsyncTask
的异步任务还没有执行完毕,那么 AsyncTask
对象就不会被释放,它所持有的 Activity
对象也不会被释放,导致 Activity
对象无法被及时回收,这就会造成内存泄漏。
解决办法:使用静态内部类来子类化 AsyncTask
,如果子类内部要使用到 Activity
,采用弱引用的方式来引用 Activity
的实例;在 Activity
结束的时候,及时 cancel
掉 AsyncTask
任务。
2.2.7 如何让 AsyncTask 并行执行任务呢?
我们知道,AsyncTask
内部默认是串行执行任务的,那么有没有办法让它并行执行任务呢?有的。
使用 AsyncTask
的 executeOnExecutor
方法,来从外部指定线程池,避免使用内部默认的 SERIAL_EXECUTOR
串行线程池。
public void parallel_execute(View view) { new MyAsyncTask("AsyncTask#1").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, ""); new MyAsyncTask("AsyncTask#2").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, ""); new MyAsyncTask("AsyncTask#3").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, ""); new MyAsyncTask("AsyncTask#4").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");}
打印日志:
D/AsyncTaskActivity: AsyncTask#2 finished at 2022-01-29 15:27:28D/AsyncTaskActivity: AsyncTask#3 finished at 2022-01-29 15:27:28D/AsyncTaskActivity: AsyncTask#1 finished at 2022-01-29 15:27:28D/AsyncTaskActivity: AsyncTask#4 finished at 2022-01-29 15:27:28
可以看到,确实并行执行了。
2.2.8 HandlerThread 的工作过程是什么?
HandlerThread
用于创建一个拥有一个 Looper
对象的新线程。开发者可以使用 HandlerThread
的 Looper
对象去创建 Handler
对象。注意需要调用 start()
方法来开启 HandlerThread
。
把 HandlerThread
的源码分为三个部分来看:创建 Looper
,获取 Looper
和终止 Looper
。
创建 Looper
在调用 start
方法后,会执行 run
方法:
@Overridepublic void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1;}
通过 Looper.prepare()
方法来创建一个对应于线程的 Looper
对象;
进入同步代码块,通过 Looper.myLooper()
获取本线程的 Looper
对象,赋值给 HandlerThread
的成员变量 mLooper
,调用 notifyAll()
唤醒其他等待锁的线程;
调用 onLooperPrepared
方法,表示 HandlerThread
已经准备好 Looper
对象了;
调用 Looper.loop()
方法开启消息循环。
获取 Looper
public Looper getLooper() { if (!isAlive()) { return null; } // 如果 HandlerThread 线程已经开启,那么 Looper 对象没有创建好,就会一直等待。 synchronized (this) { while (isAlive() && mLooper == null) { try { wait(); } catch (InterruptedException e) { } } } return mLooper;}
先判断 HandlerThread
线程对象是否存活,如果就不存活了,就直接返回 null
;
接着进入同步代码块,执行 wait()
操作,使用 while
来包裹它,循环条件是线程存活以及 mLooper
为 null
,换句话说,如果线程死亡或者 mLooper
不为 null
,就不会处于等待了。
什么时候会有这样的场景呢?这里构建一个场景:
static class MyHandlerThread extends HandlerThread { public MyHandlerThread(String name) { super(name); } @Override public void run() { // 故意先休眠 3s,再执行父类的 run 方法 SystemClock.sleep(3000L); super.run(); }}public void handlerThread(View view) { final HandlerThread handlerThread = new MyHandlerThread("WorkThread"); handlerThread.start(); // 开启 Thread1,在它的 run 方法里去获取 handlerThread 的 looper 对象。 new Thread(new Runnable() { @Override public void run() { Log.d(TAG, "run: threadName=" + Thread.currentThread().getName()); Looper looper = handlerThread.getLooper(); Log.d(TAG, "run: threadName=" + Thread.currentThread().getName() + ",looper=" + looper); } }, "Thread1").start();}
打印日志:
2022-01-29 08:30:21.650 D/MainActivity: run: threadName=Thread12022-01-29 08:30:24.653 D/MainActivity: run: threadName=Thread1,looper=Looper (WorkThread, tid 198) {2b5639a7}
当 Thread1 的 run
方法里调用 handlerThread.getLooper()
时,HandlerThread
类的 run
方法还未开始执行,所以 getLooper
方法里,Thread1 会获取锁对象,进入到同步代码块中:isAlive() && mLooper == null
条件成立,调用 Thread1 的 wait()
方法,Thread1 会进入等待状态并且释放锁对象。
过了 3s 后,HandlerThread
的 run
方法得以执行,获取到锁对象,进入到同步代码块,把创建好的 Looper
对象赋值给 mLooper
成员变量,并调用 notifyAll()
唤醒同锁的等待线程,也就唤醒了 Thread1。
Thread1 被唤醒后,再次判断isAlive() && mLooper == null
条件,此时 mLooper
不为 null
,所以循环条件不成立,退出同步代码块,直接返回 mLooper
对象。
终止 Looper
调用 HandlerThread
的 quit
方法或 quitSafely
方法来终止 Looper
。内部调用了 Looper
的 quit
和 quitSafely
方法。这是为了在不使用消息循环时,终止消息循环,避免产生内存泄漏的问题。
使用 HandlerThread
的好处:
- 可以安全方便地获取到
Looper
对象; - 可以方便地终止
Looper
。
在 Android 系统源码中,ActivityManagerService
,PackageManagerService
和 PowerManagerService
都使用到了 HandlerThread
,主要作用是把数据或者任务从主线程发送到工作线程去执行。
2.2.9 IntentService 和 Service 的区别是什么?
联系:IntentService
继承于 Service
。
区别:
IntentService
是一个抽象类,包含一个抽象方法onHandleIntent
,因此必须创建它的子类才能创建IntentService
,而Service
是一个具体类。IntentService
可以异步执行耗时的任务,当任务执行完毕后它会自动停止;而Service
是在主线程运行的,不可以直接执行耗时任务,必须自己单独开启工作线程来执行耗时任务。
2.2.10 IntentService 的工作过程是什么?
子类化 IntentService
因为 IntentService
是一个包含抽象方法 onHandleIntent
的抽象类,所以子类需要实现 IntentService
的抽象方法 onHandleIntent
方法;
因为 IntentService
定义了一个带参构造器,就不再有无参构造器,所以子类需要显式地调用父类的带参构造器。
public class MyIntentService extends IntentService { private static final String TAG = MyIntentService.class.getSimpleName(); public MyIntentService() { super(TAG); } @Overrideprotected void onHandleIntent(Intent intent) { }}
public abstract class IntentService extends Service { private String mName; public IntentService(String name) { super(); mName = name; }}
传给父类构造器的 name
,会用于创建 HandlerThread
这个线程(不要忘记了,HandlerThread
是继承于 Thread
类的)对象的线程名字。
回调 IntentService
的 onCreate()
方法
private volatile Looper mServiceLooper;private volatile ServiceHandler mServiceHandler;@Overridepublic void onCreate() { super.onCreate(); HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); thread.start(); mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper);}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); }}
可以看到,在 onCreate
方法中:
- 创建并开启一个
HandlerThread
类型的线程,这是为了获取一个对应于这个线程的Looper
对象; - 通过
HandlerThread
对象获取Looper
对象,并赋值给mServiceLooper
成员变量; - 使用
mServiceLooper
来创建一个ServiceHandler
对象,并赋值给mServiceHandler
成员变量。
需要注意的是,ServiceHandler
是 IntentService
的内部类,而不是静态内部类,这是因为在 handleMessage
中需要调用 Service
对象的 stopSelf(int startId)
方法。内部类默认会持有外部类的引用,而静态内部类并不会持有外部类的引用。
回调 IntentService
的 onStartCommand()
方法
@Overridepublic int onStartCommand(Intent intent, int flags, int startId) { onStart(intent, startId); return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;}@Overridepublic void onStart(Intent intent, int startId) { // 在主线程中发送消息 Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; mServiceHandler.sendMessage(msg);}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); }}
应当知道,每调用一次 startService
方法,对应 Service
的 onStartCommand
方法都会被调用一次。
内部调用了 onStart
方法,这是为了兼容处理的:onStart
属于废弃方法,onStartCommand
属于替代它的新方法;而把主要代码都放到 onStart
方法里面,就可以兼顾新旧版本了。
需要说明的是,Service
的生命周期方法都是在主线程调用的。因此,看 onStart
方法内部的代码,就是在主线程里面使用 mServiceHandler
发送一个消息(封装了 startId
和 intent
)。那么,接收消息的地方在哪里呢?在 ServiceHandler
的 handleMessage
方法里,这个方法是在子线程回调的。这样,就把消息(封装了 startId
和 intent
)从主线程切换到了子线程中了。
在 handleMessage
方法中,通过 Message
对象取出 obj
,即 Intent
对象,取出 startId
。这里的 Intent
对象的内容和 startService(Intent service)
中的 Intent
对象的内容是完全一致的。因此,在 onHandleIntent
方法中可以通过 Intent
对象解析出启动 Service
时所传递的参数,如下载链接,任务id等,来区分具体的异步任务。
在 onHandleIntent
方法执行完毕后,会接着执行 stopSelf(int startId)
方法,作用仅仅是尝试停止服务,也就是说,在满足条件时,就会真正地停止服务,在不满足条件时,就不会停止服务。那么,这个条件是什么?当最近启动服务的次数和 startId
相等,就会立即停止服务;不相等,就不停止服务。
2.2.11 IntentService 是如何保证多个异步任务的顺序执行的?
当多次调用 startService
方法,每次调用时让 IntentService
执行不同的异步任务,IntentService
内部会通过 Handler
把任务发送到子线程所持有的消息队列中,而子线程对应的 Looper
对象会顺序处理消息的,也就是说,在通过 onHandleIntent
方法处理完一个异步任务后,会从消息队列中取出下一个消息(封装了异步任务的参数),再通过 onHandleIntent
方法来处理对应的异步任务。这样就保证了,当多个异步任务都发送到 IntentService
时,这些异步任务的执行会按照发送的顺序一个一个地执行,而不会交叉,或者说并行。
这里通过代码实例来演示说明:
public class MyIntentService extends IntentService { private static final String TAG = MyIntentService.class.getSimpleName(); public static final String EXTRA_TASK = "extra_task"; public MyIntentService() { super(TAG); } @Override public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate: "); } @Override public void onStart(Intent intent, int startId) { Log.d(TAG, "onStart: startId = " + startId); super.onStart(intent, startId); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "onStartCommand startId:" + startId); return super.onStartCommand(intent, flags, startId); } @Override protected void onHandleIntent(Intent intent) { String task = intent.getStringExtra(EXTRA_TASK); Log.d(TAG, "onHandleIntent: " + task + " start"); long time; switch(task) { case "task 0": time = 4000; break; case "task 1": time = 2000; break; case "task 2": time = 5000; break; case "task 3": time = 1000; break; case "task 4": time = 3000; break; default: time = 0; break; } try { Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } Log.d(TAG, "onHandleIntent: " + task + " finish"); } @Override public void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy: "); }}
在 MainActivity
中,点击按钮发送任务:
public void intentservice(View view) { Intent service = new Intent(MainActivity.this, MyIntentService.class); for (int i = 0; i < 5; i++) { service.putExtra(MyIntentService.EXTRA_TASK, "task " + i); Log.d(TAG, "startService: task " + i); startService(service); }}
打印日志如下:
14:17:51.013 D/MainActivity: startService: task 0 14:17:51.017 D/MainActivity: startService: task 1 14:17:51.020 D/MainActivity: startService: task 2 14:17:51.025 D/MainActivity: startService: task 3 14:17:51.027 D/MainActivity: startService: task 4 14:17:51.037 D/MyIntentService: onCreate: 14:17:51.037 D/MyIntentService: onStartCommand startId:1 14:17:51.037 D/MyIntentService: onStart: startId = 1 14:17:51.038 D/MyIntentService: onHandleIntent: task 0 start 14:17:51.041 D/MyIntentService: onStartCommand startId:2 14:17:51.041 D/MyIntentService: onStart: startId = 2 14:17:51.042 D/MyIntentService: onStartCommand startId:3 14:17:51.042 D/MyIntentService: onStart: startId = 3 14:17:51.042 D/MyIntentService: onStartCommand startId:4 14:17:51.042 D/MyIntentService: onStart: startId = 4 14:17:51.043 D/MyIntentService: onStartCommand startId:5 14:17:51.043 D/MyIntentService: onStart: startId = 5 14:17:55.041 D/MyIntentService: onHandleIntent: task 0 finish 14:17:55.043 D/MyIntentService: onHandleIntent: task 1 start 14:17:57.044 D/MyIntentService: onHandleIntent: task 1 finish 14:17:57.046 D/MyIntentService: onHandleIntent: task 2 start 14:18:02.049 D/MyIntentService: onHandleIntent: task 2 finish 14:18:02.050 D/MyIntentService: onHandleIntent: task 3 start 14:18:03.053 D/MyIntentService: onHandleIntent: task 3 finish 14:18:03.055 D/MyIntentService: onHandleIntent: task 4 start 14:18:06.058 D/MyIntentService: onHandleIntent: task 4 finish 14:18:06.064 D/MyIntentService: onDestroy:
可以看到 14:17:51 的多行日志都是迅速打印的,这主要是发送任务相关的。
后面的日志是任务一个一个顺序执行的打印。
任务名字 | 任务需要执行的时间(毫秒) | 任务执行开始时间 | 任务执行结束时间 |
---|---|---|---|
task 0 | 4000 | 14:17:51.038 | 14:17:55.041 |
task 1 | 2000 | 14:17:55.043 | 14:17:57.044 |
task 2 | 5000 | 14:17:57.046 | 14:18:02.049 |
task 3 | 1000 | 14:18:02.050 | 14:18:03.053 |
task 4 | 3000 | 14:18:03.055 | 14:18:06.058 |
2.2.12 从源码上找出 Service 的 stopSelf() 和 stopSelf(int startId) 的区别在哪里?
public final void stopSelf() { stopSelf(-1);}public final void stopSelf(int startId) { if (mActivityManager == null) { return; } try { mActivityManager.stopServiceToken( new ComponentName(this, mClassName), mToken, startId); } catch (RemoteException ex) { }}
可以看到:stopSelf()
方法仅仅是 stopSelf(-1)
的封装而已。
那么,startId
到底是什么含义呢?
根据 2.2.11 的日志:
14:17:51.037 D/MyIntentService: onStartCommand startId:1 14:17:51.041 D/MyIntentService: onStartCommand startId:2 14:17:51.042 D/MyIntentService: onStartCommand startId:3 14:17:51.042 D/MyIntentService: onStartCommand startId:4 14:17:51.043 D/MyIntentService: onStartCommand startId:5
可以看到,调用 5 次 startService
,startId
的值从 1 增加到了 5。这是为什么呢?
在继续阅读下文之前,希望同学们了解 Service
的启动过程。如果不了解的话,可以参考笔者的文章:Android筑基——Service的启动过程之同进程启动(基于api21)。
lastStartId
的递增
在 ActiveServices
的 startServiceLocked
方法里面:
// 在获取到 ServiceRecord r对象之后,会给它的 pendingStarts 添加一个元素。r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), service, neededGrants));
查看 ServiceRecord
的 makeNextStartId
方法
public int makeNextStartId() { lastStartId++; if (lastStartId < 1) { lastStartId = 1; } return lastStartId;}
这个方法的作用:对 lastStartId
自增后并返回。具体而言:启动 1 次 MyIntentService
,lastStartId
的值就是 1;启动 2 次 MyIntentService
,lastStartId
的值就是 2。因此,lastStartId
的含义是最近启动服务的次数。
接收startId
在 sendServiceArgsLocked
方法中,会通过跨进程调用的方式,把 lastStartId
发送到 onStartCommand
方法的 startId
参数里面。
private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg, boolean oomAdjusted) { // 遍历 ServiceRecord 的 pendingStarts 列表 while (r.pendingStarts.size() > 0) { try { ServiceRecord.StartItem si = r.pendingStarts.remove(0); r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent); } catch (RemoteException e) { } }}
到这里,我们知道,onStartCommand
方法的startId
值就是当次的 ServiceRecord
的 lastStartId
值。
AMS 的 stopServiceToken 方法
public boolean stopServiceToken(ComponentName className, IBinder token, int startId) { synchronized(this) { return mServices.stopServiceTokenLocked(className, token, startId); }}
ActiveServices 的 stopServiceTokenLocked 方法
boolean stopServiceTokenLocked(ComponentName className, IBinder token, int startId) { ServiceRecord r = findServiceLocked(className, token, UserHandle.getCallingUserId()); if (r != null) { if (startId >= 0) { // 如果最近启动服务的次数 和 startId 不相等,就退出方法执行。 if (r.getLastStartId() != startId) { return false; } } // 去停止服务 bringDownServiceIfNeededLocked(r, false, false); Binder.restoreCallingIdentity(origId); return true; } return false;}
当 startId
的值为 -1 时,不会满足 startId >= 0
这一条件,所以会直接去调用停止服务;
当 startId
的值大于等于 0 时,只有 startId
的值等于最近启动服务的次数时,才会去调用停止服务。
2.3 Android 中的线程池
2.3.1 ThreadPoolExecutor 的构造方法中各个参数的含义是什么?
public ThreadPoolExecutor(int corePoolSize,// 第 1 个参数 int maximumPoolSize,// 第 2 个参数 long keepAliveTime,// 第 3 个参数 TimeUnit unit,// 第 4 个参数 BlockingQueue<Runnable> workQueue,// 第 5 个参数 ThreadFactory threadFactory,// 第 6 个参数 RejectedExecutionHandler handler) { // 第 7 个参数
序号 | 参数名 | 参数类型 | 参数含义 | 取值范围 | 解释说明 |
---|---|---|---|---|---|
1 | corePoolSize |
int |
核心线程数 | >=0 | 如果等于 0,则任务执行完成后,没有任何请求进入时就销毁线程池的线程;如果大于 0,即使本地任务执行完毕,核心线程也不会销毁。这个值的设置非常关键,设置过大会浪费资源,设置过小会导致线程频繁地创建或销毁。 |
2 | maximumPoolSize |
int |
线程池能够容纳同时执行的最大线程数 | >0并且>= corePoolSize |
如果任务数超过第 5 个参数 workQueue 的任务缓存上限且待执行的线程数小于 maximumPoolSize 时,需要借助第 5 个参数的帮助,缓存在队列中。如果 maximumPoolSize 与 corePoolSize 相等,则是固定大小线程池。最大线程数 = 核心线程数 + 非核心线程数。 |
3 | keepAliveTime |
long |
线程池中的线程空闲时间 | >=0 | 当空闲时间达到 keepAliveTime 值时,非核心线程会被销毁,直到只剩下 corePoolSize 个线程为止,避免浪费内存和句柄资源。在默认情况下,当线程池的线程数大于 corePoolSize 时,keepAliveTime 才会起作用。但是当 ThreadPoolExecutor 的 allowCoreThreadTimeOut 变量设置为 true 时,核心线程超时后也会被回收。 |
4 | unit |
TimeUnit |
时间单位 | keepAliveTime 的时间单位通常是 TimeUnit.SECONDS 。 |
|
5 | workQueue |
BlockingQueue |
任务缓存队列 | 不可以为 null |
当请求的线程数大于等于 corePoolSize 时,任务会进入 BlockingQueue 阻塞队列等待执行。 |
6 | threadFactory |
ThreadFactory |
线程工厂 | 不可以为 null |
用来生成一组相同任务的线程。线程池的命名是通过给这个 factory 增加组名前缀来实现的。在虚拟机栈分析时,就可以知道线程任务是由哪个线程工厂产生的。 |
7 | handler |
RejectedExecutionHandler |
执行拒绝策略的对象 | 不可以为 null |
当待执行的线程数大于等于 maximumPoolSize 时,就可以通过该策略处理请求,这是一种简单的限流保护。 |
2.3.2 ThreadPoolExecutor 执行任务的规则是什么?
- 如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务;
- 如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行;
- 如果在步骤 2 中无法将任务插入到任务队列中(原因往往是任务队列已满),这个时候如果线程数量未达到线程池规定的最大值,那么会启动一个非核心线程来执行任务;
- 如果步骤 3 中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务。
绘制流程图如下:
2.3.3 ThreadPoolExecutor 的 UML 类图是什么?
- 顶层接口
Executor
提供了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如何调度线程来执行任务,用户只需要提供Runnable
对象,将任务的运行逻辑提交到执行器(Executor)中,由 Executor 框架完成线程的调度和任务的执行部分。 ExecutorService
接口继承了Executor
接口,增加了一些能力:(1)扩充执行任务的能力,补充可以为一个或一批异步任务生成Future
的方法;(2)提供了管控线程池的方法,如停止线程池的运行,判断线程池是否在运行。AbstractExecutorService
是上层的抽象类,提供了对ExecutorService
接口的默认实现(submit
方法、invokeAll
方法),添加了newTaskFor
方法来返回一个RunnableFuture
,没有对核心方法execute
进行实现。它的作用是将执行任务的流程串联了起来,保证下层的实现只需要关注一个执行任务的方法即可。ThreadPoolExecutor
继承于AbstractExecutorService
类,是一个具体实现类,它一方面维护自身的生命周期,另一方面同时管理任务和线程,使两者良好地结合从而执行并行任务。ScheduledExecutorService
接口继承了ExecutorService
接口,增加可安排在给定的延迟后运行或定期执行的能力。ScheduledThreadPoolExecutor
继承于ThreadPoolExecutor
类并实现了ScheduledExecutorService
接口,在ThreadPoolExecutor
的基础上扩展了可安排在给定的延迟后运行或定期执行的能力。
2.3.4 Executors 里的线程池有哪些?这些线程池有什么特点?
比较项 | newCachedThreadPool | newFixedThreadPool(int nThreads) | newSingleThreadExecutor | newScheduledThreadPool(int corePoolSize) |
---|---|---|---|---|
corePoolSize | 0 |
nThreads |
1 | corePoolSize |
maximumPoolSize | Integer.MAX_VALUE |
nThreads |
1 | Integer.MAX_VALUE |
keepAliveTime | 60L |
0L |
0L |
0 |
unit | TimeUnit.SECONDS |
TimeUnit.MILLISECONDS |
TimeUnit.MILLISECONDS |
TimeUnit.NANOSECONDS |
workQueue | new SynchronousQueue() |
new LinkedBlockingQueue() |
new LinkedBlockingQueue() |
new DelayedWorkQueue() |
线程池名称 | 无界线程池,可以进行自动线程回收 | 固定大小线程池 | 单线程线程池 | 执行定时任务,重复任务线程池 |
特点 | 没有核心线程,只有非核心线程(最大为Integer.MAX_VALUE ),超过 60s 的空闲线程会被回收,SynchronousQueue 会将任务直接提交给线程而不保持它们,所以任务会立即执行。 |
只有固定个数的核心线程,没有非核心线程,核心线程不会被回收,任务队列大小没有限制。当线程处于空闲状态时,不会被回收;当所有的线程都处于活动状态时,新任务到达就处于等待状态,直到有线程空闲出来。 | 只有一个核心线程,没有非核心线程,核心线程不会被回收,任务队列大小没有限制。可以保证所有的任务都在同一个线程中按顺序执行。 | 核心线程数是固定的,非核心线程数是没有限制的,非核心线程空闲时会被立即回收。 |
3. 最后
本文总结了 Android 中的线程和线程池。
限于篇幅对于线程池的总结没有深入,如线程池到底是如何复用线程的?线程池的源码分析?线程池里面如何体现生产者和消费者模式?对于这些内容,同学们可以查看参考部分的文章,这些文章都是值得多多研读的。
参考
-
Architecture(1)AsyncTask源码分析
-
【源码】康一康过时的→AsyncTask
说明了 AsyncTask 的版本演进
-
android Service的stopself方法
-
Java线程池实现原理及其在美团业务中的实践
大厂出品,值得研读。
-
每日一问 | 线程池中的线程是如何复用的?
-
Android 线程池全解析-Android轮子哥
从源码上说明了复用线程到底是怎么回事。
-
池化思想
使用水龙头的例子来说明池化思想,赞!
-
JDK ThreadPoolExecutor核心原理与实践
vivo 出品