asynctask源码
⑴ 为什么都不建议直接使用 @Async 注解实现异步
对于异步方法调用,从 Spring3 开始提供了 @Async 注解,该注解可以被标注在方法上,以便异步地调用该方法。调用者将在调用时立即返回,方法的实际执行将提交给 Spring TaskExecutor 的任务中,由指定的线程池中的线程执行。
在实际项目中, 使用 @Async 调用线程池,推荐等方式是是使用自定义线程池的模式,自定义线程池常用的方案:重新实现 AsyncConfigurer 接口。
场景同步: 同步就是整个处理过程顺序执行,当各个过程都执行完毕,并返回结果。
异步: 异步调用则是只是发送了调用的指令,调用者无需等待被调用的方法完全执行完毕;而是继续执行下面的流程。例如, 在某个调用中,需要顺序调用 A, B, C 三个过程方法;如他们都是同步调用,则需要将他们都顺序执行完毕之后,方算作过程执行完毕;如 B 为一个异步的调用方法,则在执行完 A 之后,调用 B,并不等待 B 完成,而是执行开始调用 C,待 C 执行完毕之后,就意味着这个过程执行完毕了。在 java 中,一般在处理类似的场景之时,都是基于创建独立的线程去完成相应的异步调用逻辑,通过主线程和不同的业务子线程之间的执行流程,从而在启动独立的线程之后,主线程继续执行而不会产生停滞等待的情况。
Spring 已经实现的线程池SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,默认每次调用都会创建一个新的线程
SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方。
ConcurrentTaskExecutor:Executor 的适配类,不推荐使用。如果 ThreadPoolTaskExecutor 不满足要求时,才用考虑使用这个类。
SimpleThreadPoolTaskExecutor:是 Quartz 的 SimpleThreadPool 的类。线程池同时被 quartz 和非 quartz 使用,才需要使用此类。
ThreadPoolTaskExecutor :最常使用,推荐。其实质是对 java.util.concurrent.ThreadPoolExecutor 的包装。
常见的异步方式有:最简单的异步调用,返回值为 void。
带参数的异步调用,异步方法可以传入参数。
存在返回值,常调用返回 Future/CompletableFuture。
@Async 应用默认线程池Spring 应用默认的线程池,指在 @Async 注解在使用时,不指定线程池的名称。查看源码,@Async 的默认线程池为 SimpleAsyncTaskExecutor。
无返回值的异步调用@Override@Async("taskExecutor")public void pageExportOrderBigExcel(HttpServletResponse response, JSONObject queryConditionDataJson, SdSchoolFilterConfig sdSchoolFilterConfig, LoginUser loginUser, SdSchoolDataExportTaskRecord exportTask, HttpServletRequest request, String tenantId) {try {Thread.sleep(1000);exportTask.setExportTaskStartTime(new Date());sdSchoolOrderService.exportOrderBigExcelPage(response, queryConditionDataJson, exportTask, sdSchoolFilterConfig.getFilterName(), loginUser, request, tenantId);exportTask.setExportTaskEndTime(new Date());exportTaskRecordService.updateById(exportTask);} catch (Exception e) {log.error("订单数据分页导出失败", e); }}默认线程池的弊端在线程池应用中,参考阿里巴巴 java 开发规范:线程池不允许使用 Executors 去创建,不允许使用系统默认的线程池,推荐通过 ThreadPoolExecutor 的方式,这样的处理方式让开发的工程师更加明确线程池的运行规则,规避资源耗尽的风险。Executors 各个方法的弊端:
newFixedThreadPool 和 newSingleThreadExecutor:主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM。
newCachedThreadPool 和 newScheledThreadPool:要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。
@Async 默认异步配置使用的是 SimpleAsyncTaskExecutor,该线程池默认来一个任务创建一个线程,若系统中不断的创建线程,最终会导致系统占用内存过高,引发 OutOfMemoryError 错误。针对线程创建问题,SimpleAsyncTaskExecutor 提供了限流机制,通过 concurrencyLimit 属性来控制开关,当 concurrencyLimit>=0 时开启限流机制,默认关闭限流机制即 concurrencyLimit=-1,当关闭情况下,会不断创建新的线程来处理任务。基于默认配置,SimpleAsyncTaskExecutor 并不是严格意义的线程池,达不到线程复用的功能。
@Async 应用自定义线程池自定义线程池,可对系统中线程池更加细粒度的控制,方便调整线程池大小配置,线程执行异常控制和处理。在设置系统自定义线程池代替默认线程池时,虽可通过多种模式设置,但替换默认线程池最终产生的线程池有且只能设置一个(不能设置多个类继承 AsyncConfigurer)。自定义线程池有如下方式:
重新实现接口 AsyncConfigurer;
继承 AsyncConfigurerSupport;
配置由自定义的 TaskExecutor 替代内置的任务执行器。
通过查看 Spring 源码关于 @Async 的默认调用规则,会优先查询源码中实现 AsyncConfigurer 这个接口的类,实现这个接口的类为 AsyncConfigurerSupport。但默认配置的线程池和异步处理方法均为空,所以,无论是继承或者重新实现接口,都需指定一个线程池。且重新实现 public Executor getAsyncExecutor () 方法。
实现接口 AsyncConfigurer@Configuration public class AsyncConfiguration implements AsyncConfigurer { @Bean("taskExecutor") public ThreadPoolTaskExecutor executor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); int corePoolSize = 10; executor.setCorePoolSize(corePoolSize); int maxPoolSize = 50; executor.setMaxPoolSize(maxPoolSize); int queueCapacity = 10; executor.setQueueCapacity(queueCapacity); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.setThreadNamePrefix( "asyncServiceExecutor-"); executor.(true); executor.setAwaitTerminationSeconds(awaitTerminationSeconds); executor.initialize(); return executor; } @Override public Executor getAsyncExecutor() { return executor(); } }继承 @EnableAsyncclass SpringAsyncConfigurer extends AsyncConfigurerSupport {@Beanpublic ThreadPoolTaskExecutor asyncExecutor() {ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();threadPool.setCorePoolSize(3);threadPool.setMaxPoolSize(3);threadPool.(true);threadPool.setAwaitTerminationSeconds(60 * 15);return threadPool;}@Overridepublic Executor getAsyncExecutor() {return asyncExecutor;}}配置自定义的 TaskExecutor (建议采用方式)/** * 线程池参数配置,多个线程池实现线程池隔离,@Async注解,默认使用系统自定义线程池,可在项目中设置多个线程池,在异步调用的时候,指明需要调用的线程池名称,比如:@Async("taskName") * * @author: jacklin * @since: 2021/5/18 11:44 **/@EnableAsync@Configurationpublic class TaskPoolConfig {/** * 异步导出 * * @author: jacklin * @since: 2021/11/16 17:41 **/@Bean("taskExecutor")public Executor taskExecutor() {//返回可用处理器的Java虚拟机的数量 12int i = Runtime.getRuntime().availableProcessors();System.out.println("系统最大线程数: " + i);ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//核心线程池大小executor.setCorePoolSize(16);//最大线程数executor.setMaxPoolSize(20);//配置队列容量,默认值为Integer.MAX_VALUEexecutor.setQueueCapacity(99999);//活跃时间executor.setKeepAliveSeconds(60);//线程名字前缀executor.setThreadNamePrefix("asyncServiceExecutor -");//设置此执行程序应该在关闭时阻止的最大秒数,以便在容器的其余部分继续关闭之前等待剩余的任务完成他们的执行executor.setAwaitTerminationSeconds(60);//等待所有的任务结束后再关闭线程池executor.(true);return executor;}}多个线程池@Async 注解,使用系统默认或者自定义的线程池(代替默认线程池)。可在项目中设置多个线程池,在异步调用时,指明需要调用的线程池名称,如 @Async ("new_taskName")。
原文:https://juejin.cn/post/7099328896142671903⑵ UE4 多线程源码浅析(2——AsyncTask)
深入探讨虚幻引擎中的多线程系统,本文将重点解析AsyncTask模块,以期对如何在虚幻引擎中高效地运用异步任务有更深的理解。
在学习AsyncTask之前,我们首先需要理解虚幻引擎中的线程池机制。异步任务系统正是基于线程池构建的,了解线程池的运作机制对于理解AsyncTask至关重要。
线程池分为基类FQueuedThreadPool与子类FQueuedThreadPoolBase,后者负责定义线程池的内部数据结构,如等待的任务队列、正在执行任务的线程以及线程池中所有线程等。FQueuedThreadPoolBase类通过实现一些接口,如Create、Destroy、AddQueuedWork等,来控制线程池的创建、销毁与任务的调度。
任务(IQueuedWork)通过继承接口类实现特定功能,包括DoThreadedWork与Abandon等,分别用于执行任务与放弃队列中的任务。任务执行时,线程池中的线程(FQueuedThread)负责调用任务接口,执行具体操作。
在引擎启动时,虚幻线程池在FEngineLoop::PreInitPreStartupScreen中进行初始化,通过FQueuedThreadPool::Allocate创建三个线程池实例,分别为GThreadPool、GBackgroundPriorityThreadPool与GIOThreadPool,分别用于普通任务、优先级任务与IO操作。
接下来,我们深入探讨线程池的创建、添加任务与线程获取任务的实现细节。线程池的创建通过FQueuedThreadPool::Allocate完成,初始化线程池指定数量的线程。添加任务时,使用AddQueuedWork接口,确保线程池中的线程能够高效地获取与执行任务。线程获取任务的方式则通过ReturnToPoolOrGetNextJob接口实现,确保线程池的高效运行与任务的合理调度。
在理解了线程池机制与任务调度原理后,我们转向AsyncTask的解析。AsyncTask作为IQueuedWork的子类,用于实现异步任务的启动与管理。通过FAutoDeleteAsyncTask与FAsyncTask两个类,我们可以更好地理解如何在虚幻引擎中利用异步任务,提升应用性能与用户体验。
AsyncTask的实现细节涉及任务的启动、执行与取消等操作,以及线程池的选择与任务调度策略的优化。了解这些细节将帮助开发者更高效地构建和管理异步任务,实现复杂场景下的性能优化。
本文通过深入解析虚幻引擎中的线程池与AsyncTask模块,旨在为开发者提供一套完整的多线程系统理解框架。了解这些内部机制不仅可以提升代码质量,还能在实际项目开发中实现更高效、更灵活的资源管理与任务调度。
⑶ 在Android源码中,AsyncTask中的onPostExecute方法是何时调用的
这个简单, 一般要覆盖三个方法, 1、onPreExecute(), 高负载代码执行之前调用 ,通常用来显示一个进度条,在主线程中执行 2、doInBackGround() : onPreExecute() 执行完后调用,此方法通常就是放高负载代码的,比如远程请求,巨大数据载入等,你不用新建线程来包装此方法 AsyncTask(或子类)会自动在新线程中调用此方法 3、onPostExecute(Result), 在doInBackground完成之后调用,一般是设置结果,取消第一个方法显示的进度条。 onProgressUpdate() 一般用来更新第一个方法显示的进度条,什么下载了50% 51% 。。。 总之,子类化AsyncTask 你不用顾及线程问题, 主线程中直接new AsyncTask的子类,并调用execute就行了,一定要在主线程中调execute。 还有,这些是AsyncTask的生命周期方法,你自己不要调用。
⑷ 在Android源码中,AsyncTask中的onPostExecute方法是何时调用的
在doInBackground方法中,通过执行publishProgress方法call onProgressUpdate方法更新UI,这种方法常用来显示进度。onPostExecute方法是在doInBackground方法执行之后执行。你可以写一个接口层,然后把值传入接口层。在UI层实现接口,就可以获取到值。
⑸ Android中的线程状态 - AsyncTask详解
在操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限的系统资源,即线程不可能无限制地产生,并且 线程的创建和销毁都会有相应的开销。 当系统中存在大量的线程时,系统会通过会时间片轮转的方式调度每个线程,因此线程不可能做到绝对的并行。
如果在一个进程中频繁地创建和销毁线程,显然不是高效的做法。正确的做法是采用线程池,一个线程池中会缓存一定数量的线程,通过线程池就可以避免因为频繁创建和销毁线程所带来的系统开销。
AsyncTask是一个抽象类,它是由Android封装的一个轻量级异步类(轻量体现在使用方便、代码简洁),它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI。
AsyncTask的内部封装了 两个线程池 (SerialExecutor和THREAD_POOL_EXECUTOR)和 一个Handler (InternalHandler)。
其中 SerialExecutor线程池用于任务的排队,让需要执行的多个耗时任务,按顺序排列 , THREAD_POOL_EXECUTOR线程池才真正地执行任务 , InternalHandler用于从工作线程切换到主线程 。
1.AsyncTask的泛型参数
AsyncTask是一个抽象泛型类。
其中,三个泛型类型参数的含义如下:
Params: 开始异步任务执行时传入的参数类型;
Progress: 异步任务执行过程中,返回下载进度值的类型;
Result: 异步任务执行完成后,返回的结果类型;
如果AsyncTask确定不需要传递具体参数,那么这三个泛型参数可以用Void来代替。
有了这三个参数类型之后,也就控制了这个AsyncTask子类各个阶段的返回类型,如果有不同业务,我们就需要再另写一个AsyncTask的子类进行处理。
2.AsyncTask的核心方法
onPreExecute()
这个方法会在 后台任务开始执行之间调用,在主线程执行。 用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
doInBackground(Params...)
这个方法中的所有代码都会 在子线程中运行,我们应该在这里去处理所有的耗时任务。
任务一旦完成就可以通过return语句来将任务的执行结果进行返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。 注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress...)方法来完成。
onProgressUpdate(Progress...)
当在后台任务中调用了publishProgress(Progress...)方法后,这个方法就很快会被调用,方法中携带的参数就是在后台任务中传递过来的。 在这个方法中可以对UI进行操作,在主线程中进行,利用参数中的数值就可以对界面元素进行相应的更新。
onPostExecute(Result)
当doInBackground(Params...)执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中, 可以利用返回的数据来进行一些UI操作,在主线程中进行,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。
上面几个方法的调用顺序:
onPreExecute() --> doInBackground() --> publishProgress() --> onProgressUpdate() --> onPostExecute()
如果不需要执行更新进度则为onPreExecute() --> doInBackground() --> onPostExecute(),
除了上面四个方法,AsyncTask还提供了onCancelled()方法, 它同样在主线程中执行,当异步任务取消时,onCancelled()会被调用,这个时候onPostExecute()则不会被调用 ,但是要注意的是, AsyncTask中的cancel()方法并不是真正去取消任务,只是设置这个任务为取消状态,我们需要在doInBackground()判断终止任务。就好比想要终止一个线程,调用interrupt()方法,只是进行标记为中断,需要在线程内部进行标记判断然后中断线程。
3.AsyncTask的简单使用
这里我们模拟了一个下载任务,在doInBackground()方法中去执行具体的下载逻辑,在onProgressUpdate()方法中显示当前的下载进度,在onPostExecute()方法中来提示任务的执行结果。如果想要启动这个任务,只需要简单地调用以下代码即可:
4.使用AsyncTask的注意事项
①异步任务的实例必须在UI线程中创建,即AsyncTask对象必须在UI线程中创建。
②execute(Params... params)方法必须在UI线程中调用。
③不要手动调用onPreExecute(),doInBackground(Params... params),onProgressUpdate(Progress... values),onPostExecute(Result result)这几个方法。
④不能在doInBackground(Params... params)中更改UI组件的信息。
⑤一个任务实例只能执行一次,如果执行第二次将会抛出异常。
先从初始化一个AsyncTask时,调用的构造函数开始分析。
这段代码虽然看起来有点长,但实际上并没有任何具体的逻辑会得到执行,只是初始化了两个变量,mWorker和mFuture,并在初始化mFuture的时候将mWorker作为参数传入。mWorker是一个Callable对象,mFuture是一个FutureTask对象,这两个变量会暂时保存在内存中,稍后才会用到它们。 FutureTask实现了Runnable接口,关于这部分内容可以看这篇文章。
mWorker中的call()方法执行了耗时操作,即result = doInBackground(mParams);,然后把执行得到的结果通过postResult(result);,传递给内部的Handler跳转到主线程中。在这里这是实例化了两个变量,并没有开启执行任务。
那么mFuture对象是怎么加载到线程池中,进行执行的呢?
接着如果想要启动某一个任务,就需要调用该任务的execute()方法,因此现在我们来看一看execute()方法的源码,如下所示:
调用了executeOnExecutor()方法,具体执行逻辑在这个方法里面:
可以 看出,先执行了onPreExecute()方法,然后具体执行耗时任务是在exec.execute(mFuture),把构造函数中实例化的mFuture传递进去了。
exec具体是什么?
从上面可以看出具体是sDefaultExecutor,再追溯看到是SerialExecutor类,具体源码如下:
终于追溯到了调用了SerialExecutor 类的execute方法。SerialExecutor 是个静态内部类,是所有实例化的AsyncTask对象公有的,SerialExecutor 内部维持了一个队列,通过锁使得该队列保证AsyncTask中的任务是串行执行的,即多个任务需要一个个加到该队列中,然后执行完队列头部的再执行下一个,以此类推。
在这个方法中,有两个主要步骤。
①向队列中加入一个新的任务,即之前实例化后的mFuture对象。
②调用 scheleNext()方法,调用THREAD_POOL_EXECUTOR执行队列头部的任务。
由此可见SerialExecutor 类仅仅为了保持任务执行是串行的,实际执行交给了THREAD_POOL_EXECUTOR。
THREAD_POOL_EXECUTOR又是什么?
实际是个线程池,开启了一定数量的核心线程和工作线程。然后调用线程池的execute()方法。执行具体的耗时任务,即开头构造函数中mWorker中call()方法的内容。先执行完doInBackground()方法,又执行postResult()方法,下面看该方法的具体内容:
该方法向Handler对象发送了一个消息,下面具体看AsyncTask中实例化的Hanlder对象的源码:
在InternalHandler 中,如果收到的消息是MESSAGE_POST_RESULT,即执行完了doInBackground()方法并传递结果,那么就调用finish()方法。
如果任务已经取消了,回调onCancelled()方法,否则回调 onPostExecute()方法。
如果收到的消息是MESSAGE_POST_PROGRESS,回调onProgressUpdate()方法,更新进度。
InternalHandler是一个静态类,为了能够将执行环境切换到主线程,因此这个类必须在主线程中进行加载。所以变相要求AsyncTask的类必须在主线程中进行加载。
到此为止,从任务执行的开始到结束都从源码分析完了。
AsyncTask的串行和并行
从上述源码分析中分析得到,默认情况下AsyncTask的执行效果是串行的,因为有了SerialExecutor类来维持保证队列的串行。如果想使用并行执行任务,那么可以直接跳过SerialExecutor类,使用executeOnExecutor()来执行任务。
四、AsyncTask使用不当的后果
1.)生命周期
AsyncTask不与任何组件绑定生命周期,所以在Activity/或者Fragment中创建执行AsyncTask时,最好在Activity/Fragment的onDestory()调用 cancel(boolean);
2.)内存泄漏
3.) 结果丢失
屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask(非静态的内部类)会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。
自己是从事了七年开发的Android工程师,不少人私下问我,2019年Android进阶该怎么学,方法有没有?
没错,年初我花了一个多月的时间整理出来的学习资料,希望能帮助那些想进阶提升Android开发,却又不知道怎么进阶学习的朋友。【 包括高级UI、性能优化、架构师课程、NDK、Kotlin、混合式开发(ReactNative+Weex)、Flutter等架构技术资料 】,希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。