Java high concurrency programming practice 5, asynchronous annotation @Async custom thread pool

1. @Async annotation

The role of @Async is to process tasks asynchronously.

  1. Add @Async to the method to indicate that this method is an asynchronous method;
  2. Add @Async to the class to indicate that all methods in the class are asynchronous methods;
  3. The class using this annotation must be a Spring-managed class;
  4. The @EnableAsync annotation needs to be added to the startup class or configuration class for @Async to take effect;

When using @Async, if you do not specify the name of the thread pool, that is, do not customize the thread pool, @Async has a default thread pool, and uses Spring's default thread pool SimpleAsyncTaskExecutor.

The default configuration of the default thread pool is as follows:

  1. Default number of core threads: 8;
  2. Maximum number of threads: Integet.MAX_VALUE;
  3. The queue uses LinkedBlockingQueue;
  4. The capacity is: Integet.MAX_VALUE;
  5. Idle thread retention time: 60s;
  6. Thread pool rejection policy: AbortPolicy;

As can be seen from the maximum number of threads, in the case of concurrency, threads will be created unlimitedly, can I?

It can also be reconfigured via yml:

spring:
  task:
    execution:
      pool:
        max-size: 10
        core-size: 5
        keep-alive: 3s
        queue-capacity: 1000
        thread-name-prefix: my-executor

You can also customize the thread pool. The following @Async custom thread pool is implemented through simple code.

Second, the code example

Spring provides annotation @Async support for task scheduling and asynchronous method execution. By annotating the @Async annotation on the method, the method can be called asynchronously. Add the @Async annotation to the method that needs to be executed asynchronously, and specify the thread pool to be used. Of course, you can write @Async directly without specifying it.

1. Import POM

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

2. Configuration class

package com.nezhac.config;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
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.*;

@EnableAsync// 支持异步操作
@Configuration
public class AsyncTaskConfig {
    
    

    /**
     * com.google.guava中的线程池
     * @return
     */
    @Bean("my-executor")
    public Executor firstExecutor() {
    
    
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("my-executor").build();
        // 获取CPU的处理器数量
        int curSystemThreads = Runtime.getRuntime().availableProcessors() * 2;
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(curSystemThreads, 100,
                200, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(), threadFactory);
        threadPool.allowsCoreThreadTimeOut();
        return threadPool;
    }

    /**
     * Spring线程池
     * @return
     */
    @Bean("async-executor")
    public Executor asyncExecutor() {
    
    
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        // 核心线程数
        taskExecutor.setCorePoolSize(10);
        // 线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        taskExecutor.setMaxPoolSize(100);
        // 缓存队列
        taskExecutor.setQueueCapacity(50);
        // 空闲时间,当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
        taskExecutor.setKeepAliveSeconds(200);
        // 异步方法内部线程名称
        taskExecutor.setThreadNamePrefix("async-executor-");

        /**
         * 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略
         * 通常有以下四种策略:
         * ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
         * ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
         * ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
         * ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用 execute() 方法,直到成功
         */
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize();
        return taskExecutor;
    }
}

3、controller

package com.nezha.controller;

import com.nezha.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class UserController {
    
    

    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    @Autowired
    private UserService userService;

    @GetMapping("asyncTest")
    public void asyncTest() {
    
    
        logger.info("哪吒真帅");
        userService.asyncTest();
        asyncTest2();
        logger.info("哪吒编程,每日更新Java干货");
    }

    @Async("my-executor")
    public void asyncTest2() {
    
    
        logger.info("同文件内执行执行异步任务");
    }
}

4、service

package com.nezha.service;

public interface UserService {
    
    

    // 普通方法
    void test();

    // 异步方法
    void asyncTest();
}

service implementation class

package com.nezha.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    
    

    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

    @Override
    public void test() {
    
    
        logger.info("执行普通任务");
    }

    @Async("my-executor")
    @Override
    public void asyncTest() {
    
    
        logger.info("执行异步任务");
    }
}

insert image description here

3. It is found that the asynchronous task is executed in the same file, or it is a thread, and the @Async effect is not realized, why?

After searching thousands of Baidu, I found several reasons for the failure of @Async:

  1. A method annotated with @Async is not a public method;
  2. The return value of the annotation @Async can only be void or Future;
  3. Annotating @Async methods with static modification will also fail;
  4. No @EnableAsync annotation added;
  5. The caller and @Async cannot be in the same class;
  6. It is useless to mark @Transactional on Async methods, but it is valid to mark @Transcational on methods called by Async methods;

I will not demonstrate them one by one here, and interested friends can study them.

4. ThreadPoolTaskExecutor and ThreadPoolExecutor are used in the configuration. What is the difference between the two?

ThreadPoolTaskExecutor is in spring core package, and ThreadPoolExecutor is JUC in JDK. ThreadPoolTaskExecutor encapsulates ThreadPoolExecutor.
insert image description here

1、initialize()

Check out the initialize() method of ThreadPoolTaskExecutor

public abstract class ExecutorConfigurationSupport extends CustomizableThreadFactory
		implements BeanNameAware, InitializingBean, DisposableBean {
    
    
	...

	/**
	 * Set up the ExecutorService.
	 */
	public void initialize() {
    
    
		if (logger.isInfoEnabled()) {
    
    
			logger.info("Initializing ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
		}
		if (!this.threadNamePrefixSet && this.beanName != null) {
    
    
			setThreadNamePrefix(this.beanName + "-");
		}
		this.executor = initializeExecutor(this.threadFactory, this.rejectedExecutionHandler);
	}
	
	/**
	 * Create the target {@link java.util.concurrent.ExecutorService} instance.
	 * Called by {@code afterPropertiesSet}.
	 * @param threadFactory the ThreadFactory to use
	 * @param rejectedExecutionHandler the RejectedExecutionHandler to use
	 * @return a new ExecutorService instance
	 * @see #afterPropertiesSet()
	 */
	protected abstract ExecutorService initializeExecutor(
			ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler);

	...
}

2, initializeExecutor abstract method

Take a look at the concrete implementation class of the initializeExecutor abstract method, one of which is the ThreadPoolTaskExecutor class, check its initializeExecutor method, which uses ThreadPoolExecutor.
insert image description here

public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
		implements AsyncListenableTaskExecutor, SchedulingTaskExecutor {
    
    
		
	...
	
	@Override
	protected ExecutorService initializeExecutor(
			ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
    
    

		BlockingQueue<Runnable> queue = createQueue(this.queueCapacity);

		ThreadPoolExecutor executor;
		if (this.taskDecorator != null) {
    
    
			executor = new ThreadPoolExecutor(
					this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
					queue, threadFactory, rejectedExecutionHandler) {
    
    
				@Override
				public void execute(Runnable command) {
    
    
					Runnable decorated = taskDecorator.decorate(command);
					if (decorated != command) {
    
    
						decoratedTaskMap.put(decorated, command);
					}
					super.execute(decorated);
				}
			};
		}
		else {
    
    
			executor = new ThreadPoolExecutor(
					this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
					queue, threadFactory, rejectedExecutionHandler);

		}

		if (this.allowCoreThreadTimeOut) {
    
    
			executor.allowCoreThreadTimeOut(true);
		}

		this.threadPoolExecutor = executor;
		return executor;
	}
	
	...

}

Therefore, it can be understood that ThreadPoolTaskExecutor encapsulates ThreadPoolExecutor.

Five, the number of core threads

Why is the number of thread pool core threads in the configuration file configured as

// 获取CPU的处理器数量
int curSystemThreads = Runtime.getRuntime().availableProcessors() * 2;

Runtime.getRuntime().availableProcessors() gets the number of CPU core threads, that is, computing resources.

  • CPU-intensive, the thread pool size is set to N, which is the same as the number of threads in the CPU, which can avoid context switching between threads as much as possible, but in actual development, it is generally set to N+1, in order to prevent unexpected threads from appearing Blocking, if blocking occurs, the extra threads will continue to perform tasks to ensure the utilization efficiency of the CPU.
  • IO-intensive, the thread pool size is set to 2N. This number is measured based on business pressure. If no business is involved, it is recommended.

In practice, it is necessary to adjust the size of the specific thread pool, which can be adjusted according to the pressure measurement and the status of the machine and equipment.
If the thread pool is too large, the CPU will be constantly switched, and the performance of the entire system will not be greatly improved, but it will cause the system to slow down.

6. Thread pool execution process

insert image description here


Java high concurrency programming practice series articles

Java high concurrency programming practice 1, locks learned in those years

Java high-concurrency programming practice 2, atomicity, visibility, orderliness, stupidly indistinguishable

Java high concurrency programming practice 3, Java memory model and Java object structure

Java high concurrency programming practice 4, the underlying principles of synchronized and Lock

Nezha boutique series of articles

Summary of Java learning routes, brick movers counterattack Java architects

Summary of 100,000 words and 208 Java classic interview questions (with answers)

21 Tips for SQL Performance Optimization

Java Basic Tutorial Series

Spring Boot advanced practice
insert image description here

Guess you like

Origin blog.csdn.net/guorui_java/article/details/127018039