Spring Boot 实现异步任务执行 Async Task

Spring Boot 实现异步任务执行 Async Task

“异步”(Asynchronous)与“同步”(Synchronous)相对,异步不用阻塞当前线程来等待处理完成,而是容许后续操做,直至其它线程将处理完成,并回调通知此线程。也就是说,异步永远是非阻塞的(non-blocking)。java

同步操做的程序,会按照代码的顺序依次执行,每一行程序都必须等待上一个程序执行完成以后才能执行。哪些状况建议使用同步交互呢?例如,银行的转帐系统,对数据库的保存操做等等,都会使用同步交互操做。git

异步操做的程序,在代码执行时,不等待异步调用的语句返回结果就执行后面的程序。当任务间没有前后顺序依赖逻辑的时候,可使用异步。异步编程的主要困难在于,构建程序的执行逻辑时是非线性的,这须要将任务流分解成不少小的步骤,再经过异步回调函数的形式组合起来。github

1.同步任务执行spring

下面经过一个简单示例来直观的理解什么是同步任务执行。数据库

编写SyncTask类编程

编写一个 SyncTask 类,建立三个处理函数:doTaskA()、doTaskB()、doTaskC() 来分别模拟三个任务执行的操做,操做消耗时间分别设置为:1000ms、2000ms、3000ms。代码以下springboot

package com.easy.springboot.demo_async_task.task

import org.springframework.stereotype.Component

@Component
class SyncTask {

    fun doTaskA() {
        println("开始任务A")
        val start = System.currentTimeMillis()
        Thread.sleep(1000)
        val end = System.currentTimeMillis()
        println("结束任务A,耗时:" + (end - start) + "ms")
    }

    fun doTaskB() {
        println("开始任务B")
        val start = System.currentTimeMillis()
        Thread.sleep(2000)
        val end = System.currentTimeMillis()
        println("结束任务B,耗时:" + (end - start) + "ms")
    }

    fun doTaskC() {
        println("开始任务C")
        val start = System.currentTimeMillis()
        Thread.sleep(3000)
        val end = System.currentTimeMillis()
        println("结束任务C,耗时:" + (end - start) + "ms")
    }
}

单元测试并发

下面咱们来写一个单元测试,在测试用例中顺序执行 doTaskA()、doTaskB()、doTaskC() 三个函数。异步

package com.easy.springboot.demo_async_task

import com.easy.springboot.demo_async_task.task.AsyncTask
import com.easy.springboot.demo_async_task.task.SyncTask
import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.junit4.SpringRunner

@RunWith(SpringRunner::class)
@SpringBootTest
class DemoAsyncTaskApplicationTests {
    @Autowired lateinit var syncTask: SyncTask
    @Autowired lateinit var asyncTask: AsyncTask

    @Test
    fun testSyncTask() {
        println("开始测试SyncTask")
        val start = System.currentTimeMillis()
        syncTask.doTaskA()
        syncTask.doTaskB()
        syncTask.doTaskC()
        val end = System.currentTimeMillis()
        println("结束测试SyncTask,耗时:" + (end - start) + "ms")
    }

}

执行上面的单元测试,能够在控制台看到相似以下输出:async

开始测试SyncTask
开始任务A
结束任务A,耗时:1004ms
开始任务B
结束任务B,耗时:2005ms
开始任务C
结束任务C,耗时:3002ms
结束测试SyncTask,耗时:6012ms

任务A、任务B、任务C 依次按照其前后顺序执行完毕,总共耗时:6012ms。

2.异步任务执行

上面的同步任务的执行,虽然顺利的执行完了三个任务,但咱们能够看到执行时间比较长,是这3个任务时间的累加。若这三个任务自己之间不存在依赖关系,能够并发执行的话,同步顺序执行在执行效率上就比较差了——这个时候,咱们能够考虑经过异步调用的方式来实现“异步并发”地执行。

编写AsyncTask类

在Spring Boot中,咱们只须要经过使用@Async注解就能简单的将原来的同步函数变为异步函数,编写AsyncTask类,代码改写以下:

package com.easy.springboot.demo_async_task.task

import org.springframework.scheduling.annotation.Async
import org.springframework.scheduling.annotation.AsyncResult
import org.springframework.stereotype.Component
import java.util.*
import java.util.concurrent.Future

@Component
open class AsyncTask {

    @Async("asyncTaskExecutor")
    open fun doTaskA(): Future<String> {
        println("开始任务A")
        val start = System.currentTimeMillis()
        Thread.sleep(1000)
        val end = System.currentTimeMillis()
        println("结束任务A,耗时:" + (end - start) + "ms")
        return AsyncResult("TaskA DONE")
    }

    @Async("asyncTaskExecutor")
    open fun doTaskB(): Future<String> {
        println("开始任务B")
        val start = System.currentTimeMillis()
        Thread.sleep(2000)
        val end = System.currentTimeMillis()
        println("结束任务B,耗时:" + (end - start) + "ms")
        return AsyncResult("TaskB DONE")
    }

    @Async("asyncTaskExecutor")
    open fun doTaskC(): Future<String> {
        println("开始任务C")
        val start = System.currentTimeMillis()
        Thread.sleep(3000)
        val end = System.currentTimeMillis()
        println("结束任务C,耗时:" + (end - start) + "ms")
        return AsyncResult("TaskC DONE")
    }
}

上面的异步执行的任务,都返回一个 Future<String> 类型的结果对象 AsyncResult。这个对象中保存了任务的执行状态。咱们能够经过轮询这个结果来等待任务执行完毕,这样咱们能够在上面3个任务都执行完毕后,再继续作一些事情。

自定义线程池

其中,asyncTaskExecutor是咱们自定义的线程池。代码以下

@Configuration
open class TaskExecutorPoolConfig {
    @Bean("asyncTaskExecutor")
    open fun taskExecutor(): Executor {
        val executor = ThreadPoolTaskExecutor()
        executor.corePoolSize = 10 //线程池维护线程的最少数量
        executor.maxPoolSize = 20 //线程池维护线程的最大数量
        executor.setQueueCapacity(100)
        executor.keepAliveSeconds = 30 //线程池维护线程所容许的空闲时间,TimeUnit.SECONDS
        executor.threadNamePrefix = "asyncTaskExecutor-"
        // 线程池对拒绝任务的处理策略: CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;若是执行程序已关闭,则会丢弃该任务
        executor.setRejectedExecutionHandler(ThreadPoolExecutor.CallerRunsPolicy())
        return executor
    }
}

上面的代码中,咱们经过使用ThreadPoolTaskExecutor建立了一个线程池,同时设置了如下这些参数,说明以下表:

核心线程数10:线程池建立时候初始化的线程数
最大线程数20:线程池最大的线程数,只有在缓冲队列满了以后才会申请超过核心线程数的线程
缓冲队列100:用来缓冲执行任务的队列
容许线程的空闲时间30秒:当超过了核心线程出以外的线程在空闲时间到达以后会被销毁
线程池名的前缀:设置好了以后能够方便咱们定位处理任务所在的线程池
线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;若是执行程序已关闭,则会丢弃该任务

启用 @EnableAsync

为了让@Async注解可以生效,还须要在Spring Boot 的入口类上配置 @EnableAsync,代码以下:

@SpringBootApplication
@EnableAsync
open class DemoAsyncTaskApplication

fun main(args: Array<String>) {
    runApplication<DemoAsyncTaskApplication>(*args)
}

单元测试

一样地,咱们来编写一个单元测试用例来测试一下异步执行这3个任务所花费的时间。代码以下

package com.easy.springboot.demo_async_task

import com.easy.springboot.demo_async_task.task.AsyncTask
import com.easy.springboot.demo_async_task.task.SyncTask
import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.junit4.SpringRunner

@RunWith(SpringRunner::class)
@SpringBootTest
class DemoAsyncTaskApplicationTests {
    @Autowired lateinit var syncTask: SyncTask
    @Autowired lateinit var asyncTask: AsyncTask

    @Test
    fun testAsyncTask() {
        println("开始测试AsyncTask")
        val start = System.currentTimeMillis()

        val r1 = asyncTask.doTaskA()
        val r2 = asyncTask.doTaskB()
        val r3 = asyncTask.doTaskC()

        while (true) {
            // 三个任务都调用完成,退出循环等待
            if (r1.isDone && r2.isDone && r3.isDone) {
                break
            }
            Thread.sleep(100)
        }

        val end = System.currentTimeMillis()
        println("结束测试AsyncTask,耗时:" + (end - start) + "ms")
    }

}

咱们使用了一个死循环来等待三个任务都调用完成,当知足条件 r1.isDone && r2.isDone && r3.isDone 就退出循环等待。
执行上面的测试代码,能够在控制台看到相似以下输出

开始测试AsyncTask
开始任务A
开始任务B
开始任务C
结束任务A,耗时:1002ms
结束任务B,耗时:2004ms
结束任务C,耗时:3004ms
结束测试AsyncTask,耗时:3125ms

咱们能够看到,经过异步调用,任务A、任务B、任务C 异步执行完毕总共耗时: 3125ms。 相比于同步执行,无疑大大的减小了程序的总运行时间。

提示:本节实例工程源代码 https://github.com/EasySpring...


参考资料:
http://blog.didispace.com/spr...

相关文章
相关标签/搜索