Spring异步编程

简介

Spring中用@Async注解标注方法成为异步方法。它会在调用方当前线程之外独立一个线程执行,相当于我们直接new一个线程去执行:

new Thread(() -> System.out.println("new: " + Thread.currentThread().getName())).start();

为什么要使用异步执行?

很多时候我们需要调用一个耗时的或者非主链路的方法,它的执行结果并不重要,我们不需要阻塞等待它执行结束,再去执行后续的方法。同步的做法是任务依次执行,效率较低,任务的总时间是每个子任务的耗时总和。

采用异步执行的好处就是提高任务的执行效率,如果不用关心子任务的执行时间,任务的总时间即为主任务的执行时间。

使用

@Async

关注关键点:

  1. 指出该注解也可以标注类,代表类的所有方法都是异步方法
  2. 任意参数类型都是支持的,但是方法返回值必须是void或者Future类型。当使用Future时,你可以使用实现了Future接口的ListenableFuture接口或者CompletableFuture类与异步任务做更好的交互。如果异步方法有返回值,没有使用Future类型的话,调用方获取不到返回值。
  3. @Async可以指定一个特定的BeanName(即线程池,Executor/TaskExecutor)来执行方法,修改在类上表示类的所有方法都是用该bean来执行,方法级别的修饰高于类级别的修饰
  4. 调用异步方法类上需要配置上注解@EnableAsync
/**
 * Annotation that marks a method as a candidate for <i>asynchronous</i> execution.
 * Can also be used at the type level, in which case all of the type's methods are
 * considered as asynchronous.
 *
 * <p>In terms of target method signatures, any parameter types are supported.
 * However, the return type is constrained to either {@code void} or
 * {@link java.util.concurrent.Future}. In the latter case, you may declare the
 * more specific {@link org.springframework.util.concurrent.ListenableFuture} or
 * {@link java.util.concurrent.CompletableFuture} types which allow for richer
 * interaction with the asynchronous task and for immediate composition with
 * further processing steps.
 *
 * <p>A {@code Future} handle returned from the proxy will be an actual asynchronous
 * {@code Future} that can be used to track the result of the asynchronous method
 * execution. However, since the target method needs to implement the same signature,
 * it will have to return a temporary {@code Future} handle that just passes a value
 * through: e.g. Spring's {@link AsyncResult}, EJB 3.1's {@link javax.ejb.AsyncResult},
 * or {@link java.util.concurrent.CompletableFuture#completedFuture(Object)}.
 *
 * @author Juergen Hoeller
 * @author Chris Beams
 * @since 3.0
 * @see AnnotationAsyncExecutionInterceptor
 * @see AsyncAnnotationAdvisor
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {

    /**
     * A qualifier value for the specified asynchronous operation(s).
     * <p>May be used to determine the target executor to be used when executing this
     * method, matching the qualifier value (or the bean name) of a specific
     * {@link java.util.concurrent.Executor Executor} or
     * {@link org.springframework.core.task.TaskExecutor TaskExecutor}
     * bean definition.
     * <p>When specified on a class level {@code @Async} annotation, indicates that the
     * given executor should be used for all methods within the class. Method level use
     * of {@code Async#value} always overrides any value set at the class level.
     * @since 3.1.2
     */
    String value() default "";

}

基础使用

定义接口

test方法使用@Async注解修饰

public interface AsyncTestService {
    @Async
    void test();
}

实现接口

方法入口打印线程名,sleep后输出任务结束

@Service
public class AsyncTestServiceImpl implements AsyncTestService {

    @Override
    public void test() {
        System.out.println("测试异步调用,ThreadName:" + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("异步调用调用结束!");
    }
}

启动类

记录执行时间,并打印线程名,异步调用test方法,主线程继续输出结果,sleep后输出任务结束

@SpringBootApplication
public class SpringdemoApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(SpringdemoApplication.class, args);

        long start = System.currentTimeMillis();
        System.out.println("主线程执行,ThreadName:" + Thread.currentThread().getName());
        AsyncTestService testService = (AsyncTestService) applicationContext.getBean("asyncTestServiceImpl");
        testService.test();
        try {
            System.out.println("主线程输出:Hello World!");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程执行完毕,耗时:" + (System.currentTimeMillis() - start));
    }
}

测试结果

打印结果如下,分析:主线程执行输出“主线程执行,ThreadName:main”;testService.test()为异步执行,主线程输出“主线程输出:Hello World!”;主线程sleep,子线程异步执行test方法,输出“测试异步调用,ThreadName:task-1”;由于test方法sleep时间较短,输出“异步调用调用结束!”;最后主线程唤醒,输出“主线程执行完毕,耗时:2012”。可以看到test方法是异步执行。

主线程执行,ThreadName:main

主线程输出:Hello World!

扫描二维码关注公众号,回复: 13000647 查看本文章

测试异步调用,ThreadName:task-1

异步调用调用结束!

主线程执行完毕,耗时:2012

同步对比

将接口中的@Async注解注释掉,相当于同步执行,看运行结果

public interface AsyncTestService {
    @Async
    void test();
}

主线程执行,ThreadName:main

测试异步调用,ThreadName:main

异步调用调用结束!

主线程输出:Hello World!

主线程执行完毕,耗时:7008

可以看到耗时从2012增加到7008(具体耗时可以多测几次,取平均值,但是趋势是一致的),严重影响了效率。

自定义线程池

定义线程池Bean

@EnableAsync(mode = AdviceMode.PROXY)
@Configuration
public class AsyncThreadPoolConfig {

    /**
     * 核心线程数(默认线程数)
     */
    private static final int CORE_POOL_SIZE = 8;

    /**
     * 最大线程数
     */
    private static final int MAX_POOL_SIZE = 20;

    /**
     * 允许线程空闲时间(单位:默认为秒)
     */
    private static final int KEEP_ALIVE_TIME = 10;

    /**
     * 缓冲队列大小
     */
    private static final int QUEUE_CAPACITY = 200;

    /**
     * 线程池名前缀
     */
    private static final String THREAD_NAME_PREFIX = "async-task-pool-";

    /**
     * 当使用@Async注解时,需指定使用此线程池
     *
     * @return 线程池实例
     */
    @Bean("asyncTaskExecutor")
    public ThreadPoolTaskExecutor asyncTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(CORE_POOL_SIZE);
        executor.setMaxPoolSize(MAX_POOL_SIZE);
        executor.setQueueCapacity(QUEUE_CAPACITY);
        executor.setKeepAliveSeconds(KEEP_ALIVE_TIME);
        executor.setThreadNamePrefix(THREAD_NAME_PREFIX);

        executor.setThreadFactory(new ThreadFactory() {
            // 线程计数器
            private final AtomicInteger threadNumber = new AtomicInteger(0);

            @Override
            public Thread newThread(@NotNull Runnable runnable) {
                Thread thread = new Thread(runnable, THREAD_NAME_PREFIX + threadNumber.getAndIncrement());
                if (thread.isDaemon()) {
                    thread.setDaemon(false);
                }
                if (thread.getPriority() != Thread.NORM_PRIORITY) {
                    thread.setPriority(Thread.NORM_PRIORITY);
                }
                return thread;
            }
        });
        // 线程池对拒绝任务的处理策略,拒绝执行且抛出异常
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        // 初始化
        executor.initialize();
        return executor;
    }
}

定义接口

指定@Aysnc的value为线程池的beanName

public interface AsyncTestService {
    @Async(value = "asyncTaskExecutor")
    void test();
}

测试结果

实现累、启动类同上,测试结果如下,可以看到异步线程名改为了所配置的规则

主线程执行,ThreadName:main

主线程输出:Hello World!

测试异步调用,ThreadName:async-task-pool-0

主线程执行完毕,耗时:2022

异步调用调用结束!

对比

在自定义线程池的场景下,相比基础使用我们可以做许多配置,常用配置如下:

  1. 自定义核心线程数
  • CPU密集型:N + 1
  • IO密集型:2 * N + 1
  1. 自定义线程池缓冲队列大小
  • 如果该场景下任务处理比较频繁,就需要扩大缓冲区大小,反之则缩小
  1. 选择合适的饱和策略
  • AbortPolicy(默认策略):抛出RejectedExecutionException来拒绝新任务的处理

  • DiscardOldestPolicy:丢弃最早的未处理的任务请求

  • DiscardPolicy:直接丢弃任务,不执行

  • CallerRunsPolicy:在调用execute的线程里面执行此command,会阻塞入口

线程池执行策略

  1. 任务过来,如果当前工作线程数小于核心线程数corePoolSize,则创建一个新线程执行任务
  2. 如果当前工作线程数等于大于核心线程数,则将任务加入到阻塞队列中BlockingQueue
  3. 如果无法将任务加入阻塞队列(队列已满),则创建非corePool线程来处理任务
  4. 如果创建非corePool线程使得线程池线程数量大于最大线程数maxmumPoolSize,则执行饱和策略的RejectedExecutionHandler.rejectedExecution()

猜你喜欢

转载自blog.csdn.net/Dkangel/article/details/111933110