异步线程池使用@@

对着背影说爱祢 提交于 2020-02-27 08:45:54

简介

spring异步线程池的接口类,其实质是Java.util.concurrent.Executor

Spring 已经实现的异常线程池: 
1. SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。 
2. SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方 
3. ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类 
4. SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类 
5. ThreadPoolTaskExecutor :最常使用,推荐。 其实质是对java.util.concurrent.ThreadPoolExecutor的包装

一、异步线程池配置

1.注解方式

使用@EnableAsync就可以使用多线程;使用@Async就可以定义一个线程任务;通过spring给我们提供的ThreadPoolTaskExecutor就可以使用线程池。

自定义线程池,并在类上添加@EnableAsync 注解,线程池前缀"BizDataAccessTP-"。如果不定义,则使用系统默认的线程池。然后在需要异步的方法上使用@Async("线程池名称") 该方法就可以异步执行了。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @descrption 线程池初始化
 * version 1.0
 */
@Configuration
@EnableAsync //启动异步调用
public class ExecutorConfig {
    private static final Logger logger = LoggerFactory.getLogger(ExecutorConfig.class);
    @Bean(name="asyncServiceExecutor")//bean的名称,默认为首字母小写的方法名
    public Executor asyncServiceExecutor() {
        logger.info("start asyncServiceExecutor");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(20);//配置核心线程数
        executor.setMaxPoolSize(50);//配置最大线程数
        executor.setKeepAliveSeconds(60);//允许线程空闲时间(单位:默认为秒)     
        executor.setQueueCapacity(10000);//配置队列大小
        executor.setThreadNamePrefix("BizDataAccessTP-");//配置线程池中的线程的名称前缀
        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//线程池对拒绝任务的处理策略
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //执行初始化
        executor.initialize();
        return executor;
    }
}

备注:
// 设置拒绝策略
executor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
      @Override
      public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
           // .....
      }
});
// 使用预定义的异常处理类
// executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
下面关于线程池的配置还有一种方式,就是直接实现AsyncConfigurer接口,重写getAsyncExecutor方法即可,代码如下
@Configuration  
@EnableAsync  
public class ExecutorConfig implements AsyncConfigurer {  
  
     @Override  
     public Executor getAsyncExecutor() {  
          ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();  
          executor.setCorePoolSize(20);  
          executor.setMaxPoolSize(50);  
          executor.setQueueCapacity(1000);  
          executor.initialize();  
          return executor;  
     }  
  
     @Override  
     public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {  
          return null;  
     }  
}  

@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
 

2.XML方式

Bean文件配置: spring_async.xml 
1. 线程的前缀为xmlExecutor 
2. 启动异步线程池配置

<!-- 等价于 @EnableAsync, executor指定线程池 -->
<task:annotation-driven executor="xmlExecutor"/>
<!-- id指定线程池产生线程名称的前缀 -->
<task:executor
     id="xmlExecutor"
     pool-size="5-25"
     queue-capacity="100"
     keep-alive="120"
     rejection-policy="CALLER_RUNS"/>

3.参数说明

  • id :线程的名称的前缀 
  • pool-size:线程池的大小。支持范围”min-max”和固定值(此时线程池core和max sizes相同) 
  • queue-capacity:排队队列长度 
    • The main idea is that when a task is submitted, the executor will first try to use a free thread if the number of active threads is currently less than the core size. 
    • If the core size has been reached, then the task will be added to the queue as long as its capacity has not yet been reached. 
    • Only then, if the queue’s capacity has been reached, will the executor create a new thread beyond the core size. 
    • If the max size has also been reached, then the executor will reject the task. 
    •  By default, the queue is unbounded, but this is rarely the desired configuration because it can lead to OutOfMemoryErrors if enough tasks are added to that queue while all pool threads are busy. 
  • rejection-policy: 对拒绝的任务处理策略 
    •  In the default ThreadPoolExecutor.AbortPolicy, the handler throws a runtime RejectedExecutionException upon rejection. 
    •  In ThreadPoolExecutor.CallerRunsPolicy, the thread that invokes execute itself runs the task. This provides a simple feedback control mechanism that will slow down the rate that new tasks are submitted. 
    •  In ThreadPoolExecutor.DiscardPolicy, a task that cannot be executed is simply dropped. 
    •  In ThreadPoolExecutor.DiscardOldestPolicy, if the executor is not shut down, the task at the head of the work queue is dropped, and then execution is retried (which can fail again, causing this to be repeated.) 
  • keep-alive: 线程保活时间(单位秒) 
    • setting determines the time limit (in seconds) for which threads may remain idle before being terminated. If there are more than the core number of threads currently in the pool, after waiting this amount of time without processing a task, excess threads will get terminated. A time value of zero will cause excess threads to terminate immediately after executing a task without remaining follow-up work in the task queue()

二、线程使用

1.单线程方式

@Slf4j
public class ThreadPollTest {

    public static void main(String[] args) {

        log.info("当前线程:", Thread.currentThread().getName());
        thread();
        log.info("执行结束。");
    }

    public static void thread() {
        // 这里执行实际的业务逻辑,在这里我们就是用一个简单的遍历来模拟
        Arrays.stream(new int[]{1,2,3,4,5,6,7,8,9,10}).forEach(t -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("获取number为:" + t);
        });
    }
}

10:30:22.305 [main] INFO com.haodf.master.thread.ThreadPollTest - 当前线程:
10:30:22.459 [main] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:1
10:30:22.563 [main] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:2
10:30:22.666 [main] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:3
10:30:22.771 [main] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:4
10:30:22.874 [main] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:5
10:30:22.976 [main] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:6
10:30:23.077 [main] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:7
10:30:23.182 [main] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:8
10:30:23.287 [main] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:9
10:30:23.387 [main] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:10
10:30:23.387 [main] INFO com.haodf.master.thread.ThreadPollTest - 执行结束。

2.线程池方式

@Slf4j
public class ThreadPollTest {

    public static void main(String[] args) {

        log.info("当前线程:", Thread.currentThread().getName());
        /**
         *  这里也可以采用以下方式使用,但是使用线程池的方式可以很便捷的对线程管理(提高程序的整体性能),
         *  也可以减少每次执行该请求时都需要创建一个线程造成的性能消耗
         *  new Thread(() ->{
         *  run方法中的业务逻辑
         *  })
         *  
         * 定义一个线程池
         * 核心线程数(corePoolSize):1
         * 最大线程数(maximumPoolSize): 1
         * 保持连接时间(keepAliveTime):50000
         * 时间单位 (TimeUnit):TimeUnit.MILLISECONDS(毫秒)
         * 阻塞队列 new LinkedBlockingQueue<Runnable>()
         */
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1,5,50000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
        // 执行业务逻辑方法serviceTest()
        executor.execute(new Runnable() {
            @Override
            public void run() {
                thread();
            }
        });
        log.info("执行结束。");
    }
    
    public static void thread() {
        // 这里执行实际的业务逻辑,在这里我们就是用一个简单的遍历来模拟
        Arrays.stream(new int[]{1,2,3,4,5,6,7,8,9,10}).forEach(t -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("获取number为:" + t);
        });
    }
}

10:42:48.116 [main] INFO com.haodf.master.thread.ThreadPollTest - 当前线程:
10:42:48.120 [main] INFO com.haodf.master.thread.ThreadPollTest - 执行结束。
10:42:48.269 [pool-1-thread-1] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:1
10:42:48.369 [pool-1-thread-1] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:2
10:42:48.473 [pool-1-thread-1] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:3
10:42:48.575 [pool-1-thread-1] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:4
10:42:48.678 [pool-1-thread-1] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:5
10:42:48.779 [pool-1-thread-1] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:6
10:42:48.880 [pool-1-thread-1] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:7
10:42:48.985 [pool-1-thread-1] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:8
10:42:49.090 [pool-1-thread-1] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:9
10:42:49.192 [pool-1-thread-1] INFO com.haodf.master.thread.ThreadPollTest - 获取number为:10

程序的执行结果表明,主线程直接将结果进行了返回,然后才是线程池在执行业务逻辑,减少了请求响应时长。

3.@EnableAsync和@Async方式

我们发现如果我们在多个请求中都需要这种异步请求,每次都写这么冗余的线程池配置效率很低,所以spring为了提升开发人员的开发效率,使用@EnableAsync来开启异步的支持,使用@Async来对某个方法进行异步执行。
 

@Slf4j
@Controller
public class ThreadPollTest {

    @Autowired
    private ThreadService threadService;

    @RequestMapping(value = "/test")
    public void test() {

        log.info("当前线程:", Thread.currentThread().getName());
        threadService.thread();
        log.info("执行结束。");
    }
}
@EnableAsync
@Slf4j
@Service
public class ThreadService {

    @Async //这里进行标注为异步任务,在执行此方法的时候,会单独开启线程来执行
    public void thread() {
        // 这里执行实际的业务逻辑,在这里我们就是用一个简单的遍历来模拟
        Arrays.stream(new int[]{1,2,3,4,5,6,7,8,9,10}).forEach(t -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("获取number为:" + t);
        });
    }
}
2020-02-20 11:08:45.298  INFO 1483 --- [nio-8080-exec-3] com.haodf.master.thread.ThreadPollTest   : 当前线程:
2020-02-20 11:08:45.299  INFO 1483 --- [nio-8080-exec-3] com.haodf.master.thread.ThreadPollTest   : 执行结束。
2020-02-20 11:08:46.300  INFO 1483 --- [         task-2] com.haodf.master.thread.ThreadService    : 获取number为:1
2020-02-20 11:08:47.301  INFO 1483 --- [         task-2] com.haodf.master.thread.ThreadService    : 获取number为:2
2020-02-20 11:08:48.304  INFO 1483 --- [         task-2] com.haodf.master.thread.ThreadService    : 获取number为:3
2020-02-20 11:08:49.307  INFO 1483 --- [         task-2] com.haodf.master.thread.ThreadService    : 获取number为:4
2020-02-20 11:08:50.310  INFO 1483 --- [         task-2] com.haodf.master.thread.ThreadService    : 获取number为:5
2020-02-20 11:08:51.315  INFO 1483 --- [         task-2] com.haodf.master.thread.ThreadService    : 获取number为:6
2020-02-20 11:08:52.319  INFO 1483 --- [         task-2] com.haodf.master.thread.ThreadService    : 获取number为:7
2020-02-20 11:08:53.320  INFO 1483 --- [         task-2] com.haodf.master.thread.ThreadService    : 获取number为:8
2020-02-20 11:08:54.325  INFO 1483 --- [         task-2] com.haodf.master.thread.ThreadService    : 获取number为:9
2020-02-20 11:08:55.327  INFO 1483 --- [         task-2] com.haodf.master.thread.ThreadService    : 获取number为:10

结果与使用线程池的结果一致,但是简化了我们编写代码的逻辑,@Async注解帮我们实现了创建线程池的繁琐,提高了我们的开发效率。

注: @Async所修饰的函数不要定义为static类型,这样异步调用不会生效

@EnableAsync

@EnableAsync 此注解开户异步调用功能

 

 

@Async

@Async为异步注解放到方法上,表示调用该方法的线程与此方法异步执行,需要配合@EnableAsync注解使用。

异步的方法有3种 
1. 最简单的异步调用,返回值为void 
2. 带参数的异步调用 异步方法可以传入参数 
3. 异常调用返回Future

@Component
public class AsyncDemo {
    private static final Logger log = LoggerFactory.getLogger(AsyncDemo.class);
 
    /**
     * 最简单的异步调用,返回值为void
     */
    @Async
    public void asyncInvokeSimplest() {
        log.info("asyncSimplest");
    }
 
    /**
     * 带参数的异步调用 异步方法可以传入参数
     * 
     * @param s
     */
    @Async
    public void asyncInvokeWithParameter(String s) {
        log.info("asyncInvokeWithParameter, parementer={}", s);
    }
 
    /**
     * 异常调用返回Future
     * 
     * @param i
     * @return
     */
    @Async
    public Future<String> asyncInvokeReturnFuture(int i) {
        log.info("asyncInvokeReturnFuture, parementer={}", i);
        Future<String> future;
        try {
            Thread.sleep(1000 * 1);
            future = new AsyncResult<String>("success:" + i);
        } catch (InterruptedException e) {
            future = new AsyncResult<String>("error");
        }
        return future;
    }
}


asyncDemo.asyncInvokeSimplest();
asyncDemo.asyncInvokeWithException("test");
Future<String> future = asyncDemo.asyncInvokeReturnFuture(100);
System.out.println(future.get());

 

三、实例使用

1.异步线程使用

首先演示没有@Async,即没有异步执行的情况

@Component
public class CountNumber {

    public void PrintNumber(){
        for(int i=1; i<10; i++){
            System.out.println("i = " + i);
        }
    }
}

//@SpringBootApplication
@ComponentScan
public class Springboot3Application {

    public static void main(String[] args) throws Exception {

        ConfigurableApplicationContext context = SpringApplication.run(Springboot3Application.class, args);

    context.getBean(CountNumber.class).PrintNumber();
        for(int i=1; i<10; i++){
            System.out.println("------------------");
        }
        context.close();
    }
}

结果:
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
------------------
------------------
------------------
------------------
------------------
------------------
------------------
------------------
------------------

从输出结果中可以看出,启动类先从IOC容器中获取CountNumber的对象,然后执行该对象的PrintNumber方法,循环打印了9个数字,方法执行结束后,继续回到启动类中往下执行,因此开始执行for循环语句。从整个流程看属于顺序执行的。

有@Async,即异步执行的情况

@Component
public class CountNumber {
    @Async
    public void PrintNumber(){
        for(int i=1; i<10; i++){
            System.out.println("i = " + i);
        }
    }
}

/*@SpringBootApplication注解与@ComponentScan、@EnableAsync注解达到相同的功效*/
//@SpringBootApplication
@ComponentScan
@EnableAsync
public class Springboot3Application {

    public static void main(String[] args) throws Exception {

        ConfigurableApplicationContext context = SpringApplication.run(Springboot3Application.class, args);

        /*@Async和@EnableAsync配合使用*/
    context.getBean(CountNumber.class).PrintNumber();
        for(int i=1; i<10; i++){
            TimeUnit.MICROSECONDS.sleep(1);
            System.out.println("------------------");
        }
        context.close();
    }
}

结果:
------------------
------------------
------------------
------------------
------------------
------------------
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
------------------
------------------
------------------

 从输出结果中可以看出,spring boot在获取到IOC中的CountNumber对象后,一方面继续向下执行,执行for循环语句,另一方面获取对象后,执行对象中的PrintNumber方法。因此PrintNumber方法是与主线程是异步执行的。

2.异步线程池使用

@EnableAsync
@Slf4j
@Configuration
public class ExecutorConfig {


    /**
     * 获取业务数据的线程池
     */
    @Bean(name = "bizDataAccessTaskExecutor")//bean的名称,默认为首字母小写的方法名
    public Executor bizDataAccessTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(20);//核心线程数(默认线程数)
        executor.setMaxPoolSize(50);//最大线程数
        executor.setQueueCapacity(1_0000);//缓冲队列数
        executor.setThreadNamePrefix("BizDataAccessTP-");//线程池名前缀
        executor.initialize();//初始化
        //TODO 超过这些线程后, 需要阻塞, 可选setRejectedExecutionHandler 或者 Semaphore
        return executor;
    }
}
使用线程池1:

@Autowired
private Executor bizDataAccessTaskExecutor;

public void dealData(String indexName, String id) {

   bizDataAccessTaskExecutor.execute(() -> {
           .....
   });
}

使用线程池2:

@Async("bizDataAccessTaskExecutor")
public void service1() throws InterruptedException {
	Thread.sleep(1000); // 模拟耗时
}

 

 

 

 

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!