前言
昨天当我紧张的时候,一口气做了六十个俯卧撑,有机会你们也可以试试,紧张缓解多了。
AsyncTask
原理?怎么理解AsyncTask
?会不会出现想表达的思想和自己表达的不对?沟通无效?那不好意思了,GG
,本文说的不好的地方还望大家指出。
本文将从日常操作引申到FutureTask
,FutureTask
联想到AsyncTask
目录
一、异步数据同步
问题:异步线程下,数据获取的及时性,不是指多线程下操作数据的一个安全性。例如你通过
A
发起请求,如何通过A
拿数据,例如A.get
。
public class Demo {
private static String result = "1";
public static void main(String[] args){
new Thread(new Runnable() {
@Override
public void run() {
result = "2";
}
}).start();
System.out.println(result);
}
}
上面代码的问题相必大家都能看出来,我们想要结果result ,但是不尽人意。我们把线程看成是一个网络请求,result
是它返回的结果,那我们需要怎么处理,才能在后面拿到正确的返回值。
while
循环?可以,但是不是很友好,很耗CPU
资源,多个线程操作还需要考虑线程安全问题等等
while(result==null){ //这里代表数据是空的
等待 延迟 睡眠
}
result //拿数据
有没有很好的办法呢?可以添加个有返回值的回调,一般开发估计都会这么干吧,网络请求结果有了,直接给我回调
System.out.println(System.currentTimeMillis());
new MyThread<String>(data -> {
System.out.println(System.currentTimeMillis());
System.out.println(data);
}).start();
interface Callback{
void callback(String data);
}
static class MyThread<T> extends Thread{
private Callback callback;
private T result;
public MyThread(Callback callback){
this.callback = callback;
}
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
callback.callback("网络请求回执");
}
}
可行否,可行,但是你这个需要设置一个回调,能不能我直接拿数据?还有没有方法?有
public static void main(String[] args){
RunnerThread thread = new RunnerThread<>(() -> {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "网络请求";});
thread.start();
System.out.println(System.currentTimeMillis());
System.out.println(thread.get());
System.out.println(System.currentTimeMillis());
}
interface RunnerTask<T> {
T run();
}
static class RunnerThread<T> extends Thread{
private T result;
private RunnerTask<T> task;
private volatile boolean finished = false;
public RunnerThread(RunnerTask<T> task){
this.task = task;
}
@Override
public void run() {
synchronized (this){
result = task.run();
finished = true;
notifyAll();
}
}
public T get() {
synchronized (this){
//可能会被其它线程唤醒
while (!finished) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return result;
}
}
}
//output
1610550574299
网络请求
1610550578299
也就是说可以利用 生产者-消费者模式 解决此类问题。
其实最后的写法是FutureTask
的缩减版。FutureTask
更为强大,内部会通过CAS
操作去校验状态,状态包括新建、完成、取消、中断等。通过LockSupport.part
阻塞线程,保存数据,通过LockSupport.unpark
解除阻塞的线程
FutureTask<String> future = new FutureTask<>(() -> {
return "网络请求、下载图片";
});
new Thread(future).start();
System.out.println(future.get());//阻塞在这里,直到你数据回来
FutureTask
移步
FutureTask
可以阻塞获取结果,那么如果执行耗时操作时,是不是可以多步阻塞,例如
preCall();//方法执行前先调用
int result = futureTask.get();
if(result == 1){
doInBackground(); //执行任务
}
void doInBackground(){
int result = futureTask.get();
if(result == 2){
onPostExecute();//更新进度
}
}
然后想到AsyncTask
是不是也是类似这样的思想,这么干的?毕竟笔者已经把AsyncTask
已经忘了。。虽然后面啪啪打脸,但还是要继续学习了解一下。
二、AsyncTask
1、作用
多线程任务,耗时操作,结果将由工作线程通知主线程
2、主要方法
3、内部线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), sThreadFactory);
threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
核心线程数 1
个,最大线程数 20
个,非核心线程超时时间3
秒,不存元素的阻塞队列,拒绝策略会利用另一个线程池执行
sBackupExecutorQueue = new LinkedBlockingQueue<Runnable>();
sBackupExecutor = new ThreadPoolExecutor(
BACKUP_POOL_SIZE, BACKUP_POOL_SIZE, KEEP_ALIVE_SECONDS,
TimeUnit.SECONDS, sBackupExecutorQueue, sThreadFactory);
sBackupExecutor.allowCoreThreadTimeOut(true);
核心线程数 5
个,最大线程数 5
个,核心线程数超时时间3
秒,有界阻塞队列,大小没指定。
4、源码简要分析
执行方法会调用到如下方法
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;
}
从上面可以知道新开一个任务,状态会变成 RUNNING
,执行完后就是FINISHED
,状态初始化时是PENDING
,所以AsyncTask
只能调用一次哦。
然后任务开始前也是会调用 onPreExecute()
的
至于mWorker
和mFuture
都是在AsyncTask
的构造函数里初始化的。
执行mFuture
也是通过下面这个类
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);
}
}
}
其实可以理解为就是线程池去执行了mFuture
,那么主要关注点转移到了mFuture
身上,
我们先了解一下mWorker
和mFuture
内部创建逻辑
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
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("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
线程池执行mFuture
,它是一个FutureTask
类型对象,实现了RunnableFuture
接口,这个接口继承了和Future
类,Runnable
,所以调用run
方法,会调用到RunnableFuture
的run
方法,触发mWorker
的call
方法。
即会调用doInBackground
方法,耗时操作放在这里执行,最后通过finally
操作,执行postResult(result)
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
这里主要是切换到主线程,调用AsyncTask
的这个方法,结束任务
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
结束后会调用onPostExecute
方法。
至于onProgressUpdate()
方法,是需要通过publishProgress
方法触发的
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData); //主线程
break;
整套流程简易版就过了,对于异常考虑细节也可以继续深入,例如对最后结果的回调,通过AtomicBoolean
来保证
5、AsyncTask的Handler
构造函数中它的Handler
创建,默认是在主线程,如果在子线程中执行也不是步可以,感觉网上说的AsyncTask
类必须在主线程加载歧义很大,在哪个线程加载随意,只是它的 onPreExecute()
、onProgressUpdate()
、onPostExecute()
会在调用它的线程执行。基于业务考虑,一般使用AsyncTask
的场景都是在主线程,感觉直接说必须在主线程不合适。
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
6、Cancel方法
AsyncTask
的cancel
方法调用时机问题
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}
我们再看看publishProgress
方法
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
所以,调用了cancel
方法,再怎么publishProgress
都不管用了,它会回调onCancel
方法,这个也是主线程或者被调用方线程
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
7、串行执行
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
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);
}
}
}
第一次mActive
为空,入队,直接执行,第一个AsyncTask
执行,然后会再去mTask
中拿数据,导致mActive
为空,所以可以看出来,AsyncTask
是串行处理的,API版本 29。不管串行和并行,都是基于性能考虑,太多Task并行可有可能导致线程溢出或者其它异常问题,串行的话执行效率不高但是内存污染低。
三、反思
AsyncTask
细节不作详情分析,整体上已经知道她是怎么做的了,感兴趣可以自己深入细节。
线程切换用Handler
,异步执行通过线程池,线程安全用API
原子类,这些技术大家都知道,可是混合起来用,这种思路可能就会想不到,想要进阶,体会它的设计模式,封装方法的套路,性能的考虑点,平时还是得多问why
。
阅读源码三步法
- 领悟思想
- 放弃代码,体会作者设计框架的初衷和目的
- 把握设计
- 放弃细节,体会代码的接口和抽象类以及宏观的设计
- 体会细节
- 基于顶层的抽象设计,逐渐展开代码