@Async如何使用

关于@Async的基本知识不做讲解,只是对于使用过程中需要的注意的点做下强调。

最简单的使用

1.启动类添加@EnableAsync注解

package com.lara.springbootconfig;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

/**
 * @author lara
 */
@SpringBootApplication
@EnableAsync
public class SpringBootConfigApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootConfigApplication.class, args);
    }
}

2. 方法上添加@Async,类上添加@Component

package com.lara.springbootconfig.task;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;

import java.util.Random;
import java.util.concurrent.Future;

/**
 * @author lara
 */
@Component
@Slf4j
public class SyncTask {
    public static Random random =new Random();

    @Async
    public Future<String> doTaskOne() throws Exception {
        String threadName = Thread.currentThread().getName();
        log.info("{}开始做任务一",threadName);
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("{}完成任务一,耗时:{}毫秒",threadName,(end - start));
        return new AsyncResult<>("one done");
    }

    @Async
    public Future<String> doTaskTwo() throws Exception {
        String threadName = Thread.currentThread().getName();
        log.info("{}开始做任务二",threadName);
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("{}完成任务二,耗时:{}毫秒",threadName,(end - start));
        return new AsyncResult<>("one done");
    }

    @Async
    public Future<String> doTaskThree() throws Exception {
        String threadName = Thread.currentThread().getName();
        log.info("{}开始做任务三",threadName);
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("{}完成任务三,耗时:{}毫秒",threadName,(end - start));
        return new AsyncResult<>("one done");
    }

    @Async
    public Future<String> doTaskFour() throws Exception {
        String threadName = Thread.currentThread().getName();
        log.info("{}开始做任务四",threadName);
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("{}完成任务四,耗时:{}毫秒",threadName,(end - start));
        return new AsyncResult<>("one done");
    }
}

为什么要写四个一样的方法,是为了后续做测试用。为什么方法的返回值是Future,是为了在测试方法中捕捉到所有方法执行结束。

3.测试方法

package com.lara.springbootconfig;

import com.lara.springbootconfig.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;

import java.util.concurrent.Future;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SyncTaskTest {

    @Autowired
    private SyncTask syncTask;

    @Test
    public void test() throws Exception {
        Future<String> one = syncTask.doTaskOne();
        Future<String> two = syncTask.doTaskTwo();
        Future<String> three = syncTask.doTaskThree();
        Future<String> four = syncTask.doTaskFour();
        while (true) {
            if (one.isDone() && two.isDone() && three.isDone() && four.isDone()) {
                break;
            }
        }
    }
}

4.测试结果

可以看到四个方法启用了四个线程来执行。为什么会是这种结果呢??? 这是因为在我们不指定线程池的情况下,spring默认使用SimpleThreadPoolTaskExecutor。该线程池默认来一个任务创建一个线程。当异步任务非常多时,后果不堪设想。。。。

5.自己设置执行线程池

package com.lara.springbootconfig.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * @author lara
 * @date 2020/3/4 10:34
 */
@Configuration
public class ThreadPoolConfig {

    @Bean
    public TaskExecutor taskExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(3);
        executor.setKeepAliveSeconds(60);
        executor.setQueueCapacity(4);
        executor.setThreadNamePrefix("async-thread");
        return executor;
    }
}

上述线程池的参数含义为什么设置为2,3,4,没有为什么仅仅是为了测试使用。具体线程池参数含义以及线程的创建时机推荐阅读线程池各个参数的含义

加了上述配置后,再次运行相同的测试代码,看看结果。

只有两个线程在执行任务。为什么呢?因为我们设置的核心线程是2个,前2个任务到达时,创新新的线程执行。第3个任务到达后,放进任务队列中。第4个任务到达后也放进任务队列。只有任务队列满了之后,最多创建3个线程执行任务。

后续补充下SimpleThreadPoolTaskExecutor源码解析,解释为什么每来一个任务就创建一个线程。以及为什么自己创建的线程池名字为taskExecutor

参考博文: