Asynchronous code processing

In Spring, there are three main ways to implement asynchronous calls:

Method 1: Annotation method
To enable asynchronous support, first add the @EnableAsync annotation to the Spring Boot entry class:

@SpringBootApplication
@EnableAsync
public class DemoApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(DemoApplication.class, args);
    }
}

Create a new service package and create a TestService:

@Service
public class TestService {
    
    

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Async
    public void asyncMethod() {
    
    
        sleep();
        logger.info("异步方法内部线程名称:{}", Thread.currentThread().getName());
    }

    public void syncMethod() {
    
    
        sleep();
    }

    private void sleep() {
    
    
        try {
    
    
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

The above Service contains an asynchronous method asyncMethod (after enabling asynchronous support, you only need to add @Async annotation on the method to be an asynchronous method) and a synchronous method syncMethod. The sleep method is used to block the current thread for 2 seconds.
Because of asynchrony, the program is not blocked by the sleep method, which is the benefit of asynchronous calls. At the same time, a new thread will be started inside the asynchronous method to execute. The
default asynchronous thread pool configuration prevents threads from being reused. Every time an asynchronous method is called, a new thread will be created. We can define the asynchronous thread pool to optimize it.

@Configuration
public class AsyncPoolConfig {
    
    

    @Bean
    public ThreadPoolTaskExecutor asyncThreadPoolTaskExecutor(){
    
    
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(20);
        executor.setMaxPoolSize(200);
        executor.setQueueCapacity(25);
        executor.setKeepAliveSeconds(200);
        executor.setThreadNamePrefix("asyncThread");
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);

        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        executor.initialize();
        return executor;
    }
}

To use the thread pool, just specify the thread pool Bean name on the @Async annotation:

@Service
public class TestService {
    
    
    ......

    @Async("asyncThreadPoolTaskExecutor")
    public void asyncMethod() {
    
    
       ......
    }
    ......
}

Handling asynchronous callbacks
If the asynchronous method has a return value, you need to use Future to receive the callback value. We modify the asyncMethod method of TestService and add a return value to it:

@Async("asyncThreadPoolTaskExecutor")
public Future<String> asyncMethod() {
    
    
    sleep();
    logger.info("异步方法内部线程名称:{}", Thread.currentThread().getName());
    return new AsyncResult<>("hello async");
}

The get method of the Future interface is used to obtain the return value of the asynchronous call.
From the returned result, we can see that the get method of Future is a blocking method. Only when the asynchronous method returns the content, the program will continue to execute. get also has a get (long timeout, TimeUnit unit) overload method, we can set the timeout time through this overload method, that is, if the asynchronous method does not return a value within the set time, java.util.concurrent.TimeoutException will be thrown directly abnormal.
For example, set the timeout to 60 seconds:

String result = stringFuture.get(60, TimeUnit.SECONDS);

Method 2: Built-in thread pool method
You can use Spring's built-in thread pool to implement asynchronous calls, such as ThreadPoolTaskExecutor and SimpleAsyncTaskExecutor. Spring provides many built-in implementations of TaskExecutor. The following briefly introduces the five built-in thread pools.

1)SimpleAsyncTaskExecutor:它不会复用线程,每次调用都是启动一个新线程。

2)ConcurrentTaskExecutor:它是Java API中Executor实例的适配器。

3)ThreadPoolTaskExecutor:这个线程池是最常用的。它公开了用于配置的bean属性,并将它包装在TaskExecutor中。

4)WorkManagerTaskExecutor:它基于CommonJ WorkManager来实现的,并且是在Spring上下文中的WebLogic或WebSphere中设置CommonJ线程池的工具类。

5)DefaultManagedTaskExecutor:主要用于支持JSR-236兼容的运行时环境,它是使用JNDI获得ManagedExecutorService,作为CommonJ WorkManager的替代方案。

Usually, ThreadPoolTaskExecuto is the most commonly used. As long as ThreadPoolTaskExecutor cannot meet the requirements, ConcurrentTaskExecutor can be used. If multiple thread pools are declared in the code, Spring will call the thread pools in the following search order by default:

第一步,检查上下文中的唯一TaskExecutor Bean。

第二步,检查名为“ taskExecutor”的Executor Bean。

第三步,以上都无法无法处理,就会使用SimpleAsyncTaskExecutor来执行。

Example 1:

CompletableFuture.runAsync()来完成异步任务(标准版)

(1) Configure the thread pool

    /**
     * int corePoolSize,
     * int maximumPoolSize,
     * long keepAliveTime,
     * TimeUnit unit,
     * BlockingQueue<Runnable> workQueue,
     * ThreadFactory threadFactory,
     * RejectedExecutionHandler handler
     * @return
     */
    @Bean
    public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool) {
    
    
        return new ThreadPoolExecutor(pool.getCoreSize(),
                pool.getMaxSize(),
                pool.getKeepAliveTime(),
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(100000),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
                );
    }

(2) Thread pool parameter configuration class

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
	my.thread.core-size=20
	my.thread.max-size=200
	my.thread.keep-alive-time=10
*/
@ConfigurationProperties(prefix = "my.thread")
@Component
@Data
public class ThreadPoolConfigProperties {
    
    
	//核心线程数
    private Integer coreSize;
    //最大线程数
    private Integer maxSize;
    //空余线程的存活时间
    private Integer keepAliveTime;
}

(3) Test asynchronous tasks

	@Autowired
    private ThreadPoolExecutor executor;
	
	//在这里开启一个异步任务,提交给线程池,runAsync()方法没有返回值,需要有返回值的可使用supplyAsync()方法
    @Test
    void testCompletableFuture() {
    
    
        CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> {
    
    
            int result = 0;
            for (int i = 0; i <= 100; i++) {
    
    
                result += i;
            }
            System.out.println(result);
        }, executor);
    }

(4) Other related uses of CompletableFuture
4.1 The **get()** method of CompletableFuture can obtain asynchronous results. The get method is a blocking waiting method, that is, the get method will wait for the completion of the asynchronous task

CompletableFuture<AtomicInteger> completableFuture2 = CompletableFuture.supplyAsync(() -> {
    
    
            for (int i = 0; i <= 100; i++) {
    
    
                sum2.addAndGet(i);
            }
            return sum2;
     }, executor);
        //获取异步结果
 AtomicInteger integer = completableFuture2.get();

4.2 allOf : Wait for all tasks to complete

AtomicInteger sum = new AtomicInteger();
AtomicInteger sum2 = new AtomicInteger();
CompletableFuture<AtomicInteger> completableFuture1 = CompletableFuture.supplyAsync(() -> {
    
    
   for (int i = 0; i <= 100; i++) {
    
    
       sum.addAndGet(i);
   }
   return sum;
}, executor);


CompletableFuture<AtomicInteger> completableFuture2 = CompletableFuture.supplyAsync(() -> {
    
    
   for (int i = 0; i <= 100; i++) {
    
    
       sum2.addAndGet(i);
   }
   return sum2;
}, executor);
AtomicInteger integer = completableFuture2.get();

//allOf : 等待所有任务完成完成,注意get方法,是阻塞式等待,等待上面的异步任务都完成
CompletableFuture.allOf(completableFuture1,completableFuture2).get();

//获取异步结果
 AtomicInteger atomicInteger1 = completableFuture1.get();
 AtomicInteger atomicInteger2 = completableFuture2.get();

 System.out.println("结果是--->"+atomicInteger1.addAndGet(atomicInteger2.intValue()));

When the asynchronous task completes, whenComplete, exceptionally

CompletableFuture<AtomicInteger> completableFuture3 = CompletableFuture.supplyAsync(() -> {
    
    
        for (int i = 0; i <= 10; i++) {
    
    
            sum2.addAndGet(i);
        }
        return sum2;
    }, executor).whenComplete((res, exception) -> {
    
    
        //当出现异常,可以拿到异常信息,但是无法修改返回数据
        System.out.println("结果是:" + res + ",异常:" + exception);
    }).exceptionally(throwable -> {
    
    
        //可以感知异常,同时返回默认值
        return new AtomicInteger(10);
    });

4.4 handle, subsequent processing after the method is completed

CompletableFuture<Integer> completableFuture4 = CompletableFuture.supplyAsync(() -> {
    
    
      int i = 10 / 2;
       return i;
   }, executor).handle((res, throwable) -> {
    
    
       //res 为结果,throwable 为异常
       if (res != null) {
    
    
           return res * 2;
       }
       if (throwable != null) {
    
    
           return -1;
       }
       return 0;
   });
   System.out.println("completableFuture4--结果是:"+completableFuture4.get());

4.5 Serialization of asynchronous tasks

/**
* 异步任务串行化
*      thenAcceptAsync  可以接收上一步获取的结果,但是无返回值
*      thenApplyAsync   可以接收上一步获取的结果,有返回值
*/
   CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
    
    
       int i = 10 / 2;
       return i;
   }, executor).thenApplyAsync(res -> {
    
    
       //res为上一步的结果
       return res * 2;
   }, executor).thenAcceptAsync((res) -> {
    
    
       System.out.println("hello ...thenAcceptAsync");
   }, executor);

Example 2:

CompletableFuture.runAsync()来完成异步任务(简易版)

(1) Initialize the pool

private ExecutorService executor = Executors.newFixedThreadPool(5);

(2) use

@ApiOperation(value = "领实物奖")
    @PostMapping(Routes.API_HELP_OFFLINE_PRIZE)
    public Response getOfflinePrize(@Valid @RequestBody GetOfflinePrizeReq req, HttpServletRequest request) {
    
    
        try {
    
    
            UserInfo user = getUser(req.getToken(), request);
           
            CompletableFuture.runAsync(() -> {
    
    
                record.setPrizeName(prizes.get(0).getPrizeName());
                helpFacadeService.sendPrizes(user, record, prizes);
                helpFacadeService.recordPrizeAddress(req, user, prizes);
            }, executor);
            return ResponseUtils.buildResponse(null);
        } catch (Exception e) {
    
    
            log.error("[getPrize error] error: {}", e);
            return ResponseUtils.buildErrorResponse(SYSTEM_ERROR, "领奖失败");
        }
    }

CompletableFuture provides four static methods to create an asynchronous operation:

public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

The difference between these four methods is:

· runAsync() 以Runnable函数式接口类型为参数,没有返回结果,supplyAsync() 以Supplier函数式接口类型为参数,返回结果类型为U;Supplier接口的 get()是有返回值的(会阻塞)
· 使用没有指定Executor的方法时,内部使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。
· 默认情况下CompletableFuture会使用公共的ForkJoinPool线程池,这个线程池默认创建的线程数是 CPU 的核数(也可以通过 JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelism 来设置ForkJoinPool线程池的线程数)。如果所有CompletableFuture共享一个线程池,那么一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程饥饿,进而影响整个系统的性能。所以,强烈建议你要根据不同的业务类型创建不同的线程池,以避免互相干扰

Method 3: Custom thread pool method
You can customize the thread pool by implementing the AsyncConfigurer interface or directly inheriting the AsyncConfigurerSupport class. However, there is a slight difference in the implementation of non-fully managed beans and fully managed beans.

First, let's look at the non-fully managed Spring Bean, as shown in the code:
insert image description here

@Configuration
@EnableAsync
public class AppConfig implements AsyncConfigurer {
    
    

    @Override
    public Executor getAsyncExecutor() {
    
    
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(7);
        executor.setMaxPoolSize(42);
        executor.setQueueCapacity(11);
        executor.setThreadNamePrefix("MyExecutor-");
        executor.initialize();//手动初始化
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    
    
        return new MyAsyncUncaughtExceptionHandler();
    }
}

In this code, ThreadPoolTaskExecutor is not a fully managed Spring bean.

Then, look at the fully managed Spring Bean, as shown in the code

@Configuration
@EnableAsync
public class AppConfig implements AsyncConfigurer {
    
    

    @Bean
    @Override
    public Executor getAsyncExecutor() {
    
    
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(7);
        executor.setMaxPoolSize(42);
        executor.setQueueCapacity(11);
        executor.setThreadNamePrefix("MyExecutor-");
        //executor.initialize();//不用手动调用
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    
    
        return new MyAsyncUncaughtExceptionHandler();
    }
}

As long as the @Bean annotation is added to the asynchronous method, there is no need to manually call the initialize() method of the thread pool, which will be called automatically after the Bean is initialized. It should be noted that calling an asynchronous method directly in a class of the same level cannot achieve asynchrony.

Guess you like

Origin blog.csdn.net/u014365523/article/details/128394096
Recommended