Spring线程池与@Async相关知识

一、Spring线程池(TaskExecutor):

1.简介:

        大家都知道从JDK5开始引入JUC工具包(java.util .concurrent),JUC中包含了atomic原子类、 Lock锁、Executor框架、Callable与Future、ConcurrentMap等支持多线程高并发的Java类,程序员可以调用此包进行高效的多线程编程。Java线程池的功能就是通过JUC包中Executor框架来实现的,在Executor中为我们提供了五种线程池,比如固定大小线程池、单线程池、可以固定周期频率的线程池等。Spring中也有线程池的功能,它是通过继承java的Executor接口来实现的,Spring也提供了在不同功能场景的线程池,大概有7种,方便我们的使用。下面我们先通过一幅UML类图,了解一下Spring中各种线程池的继承关系:

Spring线程池与@Async相关知识_第1张图片

接下来我们了解一下Spring中各种线程池的功能:

线程池名称 作用及特点
SimpleAsyncTaskExecutor 

每次调用创建一个新的线程,线程不会重用;支持设置设置最大的线程数量,超过最大线程数,堵塞调用。

SyncTaskExecutor 同步线程,每次调用都在同一个线程中执行。
ConcurrentTaskExecutor Executor的包装类,不推荐使用。
SimpleThreadPoolTaskExecutor

它是Quartz中SimpleThreadPool类的子类,它会监听Spring的生命周期回调。使用场景:需要在Quartz和非Quartz组件中共用时使用。

ThreadPoolTaskExecutor

ThreadPoolExecutor的包装类,最常用Spring线程池。

TimerTaskExecutor 它使用TimerTask类作为实现。
WorkManagerTaskExecutor

它现了WorkManager接口,实现使用了CommonJ WorkManager作为其底层实现。其中,CommonJ 是BEA和IBM联合开发的一套规范。这些规范并非Java EE的标准,但它是BEA和IBM的应用服务器实现的共同标准。

 

2.使用: 

ThreadPoolTaskExecutor的注解配置方式举例:

package com.hanxiaozhang.taskexecutor;

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

/**
 * 〈一句话功能简述〉
* 〈线程池配置〉 * * @author hanxinghua * @create 2020/2/22 * @since 1.0.0 */ @Configuration public class ThreadPoolTaskExecutorConfig implements AsyncConfigurer { /** * 冒号后为默认值 */ @Value("${async.executor.core-pool-size:10}") private int corePoolSize; /** * 默认值也可以直接赋值给属性 */ @Value("${async.executor.max-pool-size}") private int maxPoolSize=100; @Value("${async.executor.queue-capacity:10}") private int queueCapacity; @Value("${async.executor.await-termination-seconds:60}") private int awaitTerminationSeconds; @Value("${async.executor.wait-for-tasks-to-complete-on-shutdown:true}") private boolean waitForTasksToCompleteOnShutdown; @Value("${async.executor.thread-name-prefix:AsyncExecutorThread-}") private String threadNamePrefix; @Bean(name = "asyncExecutor") @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor(); //设置核心线程数 threadPool.setCorePoolSize(corePoolSize); //设置最大线程数 threadPool.setMaxPoolSize(maxPoolSize); //线程池所使用的缓冲队列 threadPool.setQueueCapacity(queueCapacity); //等待任务在关机时完成--表明等待所有线程执行完 threadPool.setWaitForTasksToCompleteOnShutdown(waitForTasksToCompleteOnShutdown); // 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止 threadPool.setAwaitTerminationSeconds(awaitTerminationSeconds); // 线程名称前缀 threadPool.setThreadNamePrefix(threadNamePrefix); // 初始化线程 threadPool.initialize(); return threadPool; } /** * 多线程异常处理 * * @return */ @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return null; } }

二、@Async: 

@Async的作用是定义异步任务,它一共有三种用法:1. 最简单的无参数无返回值异步调用; 2. 带参数无返回值异步调用 3. 返回Future异步调用,下面三种用法简单Demo:

   /**
     * 1.最简单的无参数无返回值异步调用
     */
    @Async
    public void asyncSimple() {
        log.info("asyncSimple");
    }

    /**
     * 2.带参数无返回值异步调用 
     *
     * @param s
     */
    @Async
    public void asyncParameter(String s) {
        log.info("asyncParameter, parameter={}", s);
    }

    /**
     * 3.返回Future异步调用
     *
     * @param s
     * @return
     */
    @Async
    public Future asyncReturnFuture(String s) {
        log.info("asyncReturnFuture, parameter={}", s);
        Future future=new AsyncResult("futureString: " + s);
        return future;
    }

此外, @Async必须与@EnableAsync一起使用才有效果,如果没有在该类上或Springboot启动类上打@EnableAsync,异步是不生效的。大家可以通过以下例子进验证,我们模拟一个流程A,流程A一共有step1、step2和step3三个步骤,执行完step1后,step2就可以异步处理。

首先,我们看一下,不加异步处理的执行效果:

//---------------模拟流程A------------------

@Component
public class AsyncAnnotationCode {

   /**
     * 模拟步骤1,执行100ms
     *
     * @throws InterruptedException
     */
    public void step1() throws InterruptedException {
        System.out.println("step1 start");
        Thread.sleep(100);
        System.out.println("step1 end");
    }

    /**
     * 模拟步骤2,执行300ms
     *
     * @throws InterruptedException
     */
    public void step2() throws InterruptedException {
        System.out.println("step2 start");
        Thread.sleep(300);
        System.out.println("step2 end");
    }

    /**
     * 模拟步骤3,执行200ms
     *
     * @throws InterruptedException
     */
    public void step3() throws InterruptedException {
        System.out.println("step3 start");
        Thread.sleep(200);
        System.out.println("step3 end");
    }

}

 //---------------执行流程A------------------

    @Test
    public  void asyncTest() {

        try {
            long startTime = System.currentTimeMillis();
            asyncAnnotationCode.step1();
            asyncAnnotationCode.step2();
            asyncAnnotationCode.step3();
            long endTime = System.currentTimeMillis();
            System.out.println("流程耗时:"+(endTime-startTime));

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

结果: 

Spring线程池与@Async相关知识_第2张图片

通过以上测试,我们可以发现step1-3是顺序执行,耗时在600ms左右。

然后,我们只在step2添加@Async注解,看一下执行效果:

//---------------模拟流程A------------------

@Component
public class AsyncAnnotationCode {

   /**
     * 模拟步骤1,执行100ms
     *
     * @throws InterruptedException
     */
    public void step1() throws InterruptedException {
        System.out.println("step1 start");
        Thread.sleep(100);
        System.out.println("step1 end");
    }

    /**
     * 模拟步骤2,执行300ms
     *
     * @throws InterruptedException
     */
    @Async
    public void step2() throws InterruptedException {
        System.out.println("step2 start");
        Thread.sleep(300);
        System.out.println("step2 end");
    }

    /**
     * 模拟步骤3,执行200ms
     *
     * @throws InterruptedException
     */
    public void step3() throws InterruptedException {
        System.out.println("step3 start");
        Thread.sleep(200);
        System.out.println("step3 end");
    }

}

 //---------------执行流程A------------------

    @Test
    public  void asyncTest() {

        try {
            long startTime = System.currentTimeMillis();
            asyncAnnotationCode.step1();
            asyncAnnotationCode.step2();
            asyncAnnotationCode.step3();
            long endTime = System.currentTimeMillis();
            System.out.println("流程耗时:"+(endTime-startTime));

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

结果: 

Spring线程池与@Async相关知识_第3张图片

通过以上测试,只添加@Async,step1-3还是是顺序执行,耗时在600ms,并没有异步执行。

最后,我们在step2添加@Async注解,并且该在类上添加@EnableAsync注解,看一下执行效果:

//---------------模拟流程A------------------

@Component
@EnableAsync
public class AsyncAnnotationCode {

   /**
     * 模拟步骤1,执行100ms
     *
     * @throws InterruptedException
     */
    public void step1() throws InterruptedException {
        System.out.println("step1 start");
        Thread.sleep(100);
        System.out.println("step1 end");
    }

    /**
     * 模拟步骤2,执行300ms
     *
     * @throws InterruptedException
     */
    @Async
    public void step2() throws InterruptedException {
        System.out.println("step2 start");
        Thread.sleep(300);
        System.out.println("step2 end");
    }

    /**
     * 模拟步骤3,执行200ms
     *
     * @throws InterruptedException
     */
    public void step3() throws InterruptedException {
        System.out.println("step3 start");
        Thread.sleep(200);
        System.out.println("step3 end");
    }

}

 //---------------执行流程A------------------

    @Test
    public  void asyncTest() {

        try {
            long startTime = System.currentTimeMillis();
            asyncAnnotationCode.step1();
            asyncAnnotationCode.step2();
            asyncAnnotationCode.step3();
            long endTime = System.currentTimeMillis();
            System.out.println("流程耗时:"+(endTime-startTime));

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

结果:  

Spring线程池与@Async相关知识_第4张图片

通过以上测试,同时@Async和@EnableAsync后,step1-3没有按照是顺序执行,step2 start后,step3  start开始了,耗时344ms。细心的小伙伴可能发现,输出的流程耗时不正确啊,并且在输出step2 end 前就输出了流程耗时。此外,通过对模拟流程分析,能计算异步大概的耗时时间,step1耗时100ms + step2耗时300ms,大概是400ms,也证明耗时间不正确。接下来,咱们分析一下为什么会这样,我们这里要用到Thread.currentThread()方法,该方法的作用是获取当前线程信息。

分析耗时时间不正确原因:

//---------------模拟流程A------------------

@Component
@EnableAsync
public class AsyncAnnotationCode {

   /**
     * 模拟步骤1,执行100ms
     *
     * @throws InterruptedException
     */
    public void step1() throws InterruptedException {
        System.out.println("step1 start");
        System.out.println("step1 当前线程名称:"+Thread.currentThread().getName());
        Thread.sleep(100);
        System.out.println("step1 end");
    }

    /**
     * 模拟步骤2,执行300ms
     *
     * @throws InterruptedException
     */
    @Async
    public void step2() throws InterruptedException {
        System.out.println("step2 start");
        System.out.println("step2 当前线程名称:"+Thread.currentThread().getName());
        Thread.sleep(300);
        System.out.println("step2 end");
    }

    /**
     * 模拟步骤3,执行200ms
     *
     * @throws InterruptedException
     */
    public void step3() throws InterruptedException {
        System.out.println("step3 start");
        System.out.println("step3 当前线程名称:"+Thread.currentThread().getName());
        Thread.sleep(200);
        System.out.println("step3 end");
    }

}

 //---------------执行流程A------------------

    @Test
    public  void asyncTest() {

        try {
            long startTime = System.currentTimeMillis();
            System.out.println("执行方法 当前线程名称:"+Thread.currentThread().getName());
            asyncAnnotationCode.step1();
            asyncAnnotationCode.step2();
            asyncAnnotationCode.step3();
            long endTime = System.currentTimeMillis();
            System.out.println("流程耗时:"+(endTime-startTime));

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

结果: 

Spring线程池与@Async相关知识_第5张图片

通过以上结果,我们可以发现执行方法、step1、step3是在main线程,而step2是在AsyncExecutorThread-1线程,main线程代码会安装顺序执行,执行方法startTime-> step1()->step3()->endTime->输出耗时时间,所以这里耗时时间是step1+step3的大概时间300ms。细心的小伙伴可能又会发现,step2的线程名称是AsyncExecutorThread-1,看名字应该是使用了Spring线程池,是的,你没有想错,我确实配置了线程池,如果没有配置线程池,运行结果是什么样的呢?

没有配置线程池,运行上述代码的结果:

结果情况一:没有异常,但是没有输出step2  end

Spring线程池与@Async相关知识_第6张图片

结果情况二:有异常, 也没有输出step2  end

Spring线程池与@Async相关知识_第7张图片

经过一番思考,我觉得造成没有输出step2 end的原因是,采用单元测试的方式进行测试时,执行测试的代码在main线程输出流程耗时后,就结束了,main线程结束后,单元测试也就跟着结束了,所有没有输出step2 end。应该在执行测试代码中添加一个while(true)循环,让main线程永不停止,即可解决。

添加while(true)循环:

 //---------------执行流程A------------------

    @Test
    public  void asyncTest() {

        try {
            long startTime = System.currentTimeMillis();
            System.out.println("执行方法 当前线程名称:"+Thread.currentThread().getName());
            asyncAnnotationCode.step1();
            asyncAnnotationCode.step2();
            asyncAnnotationCode.step3();
            long endTime = System.currentTimeMillis();
            System.out.println("流程耗时:"+(endTime-startTime));
            //main线程一直不消亡
            while (true){

            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

结果:

 Spring线程池与@Async相关知识_第8张图片

 

 

你可能感兴趣的:(Spring)