Tutoriel de base springboot2.x: @Async ouvre des tâches asynchrones

Dans les projets de développement, nous avons généralement des scénarios dans lesquels nous devons démarrer des tâches asynchrones. Par exemple, lorsque l'utilisateur s'inscrit avec succès, certains coupons doivent être émis. À ce stade, afin d'éviter que ces opérations supplémentaires n'affectent le processus d'inscription de l'utilisateur, nous démarrons généralement un thread de manière asynchrone pour exécuter la logique de distribution des coupons.
Habituellement, nous devons définir nous-mêmes un pool de threads et démarrer une tâche de thread. Elle est simplifiée dans Springboot et un pool de threads de tâches de type org.springframework.core.task.TaskExecutor est automatiquement configuré. Lorsque nous activons l'annotation @EnableAsync, lorsque nous devons effectuer des tâches asynchrones, ajoutez l'annotation @Async, cette méthode Démarre automatiquement un thread à exécuter.
Lorsque vous expliquez comment configurer Springboot pour démarrer des tâches asynchrones, expliquons brièvement les connaissances de base du pool de threads Java.

Connaissance de base du pool de threads

Utiliser des scénarios de pool de threads

En général, le temps d'exécution de la tâche est court, le temps de création du thread est T1, le temps d'exécution de la tâche T2 et le temps de destruction du thread T3. Lorsque le temps T1 + T2 est bien supérieur à T2, le pool de threads peut être utilisé. La fonction principale est de réutiliser les threads et de réduire la surcharge de temps de création et de destruction des threads.
Comptez facilement les informations d'exécution des tâches, telles que le nombre de tâches terminées.

L'interface de niveau supérieur du pool de threads Executor

Après la version Jdk1.5, un pool de threads est fourni aux développeurs pour créer facilement leurs propres tâches multi-threadées. L'interface java.util.concurrent.Executor est l'interface de niveau supérieur du pool de threads.
L'interface execute reçoit une instance de thread Runnable pour exécuter une tâche.

public interface Executor {
    void execute(Runnable command);
}

Explication détaillée de l'interface ExecutorService

public interface ExecutorService extends Executor {

    /**关闭线程池不会接收新的任务,内部正在跑的任务和队列里等待的任务,会执行完
     */
    void shutdown();

    /**
     * 关闭线程池,不会接收新的任务
	 * 尝试将正在跑的任务interrupt中断
	 * 忽略队列里等待的任务
	 * 返回未执行的任务列表
     */
    List<Runnable> shutdownNow();

    /**
     * 返回线程池是否被关闭
     */
    boolean isShutdown();

    /**
     *线程池的任务线程是否被中断
     */
    boolean isTerminated();

    /**
	*接收timeout和TimeUnit两个参数,用于设定超时时间及单位。
	* 当等待超过设定时间时,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。和shutdown方法组合使用。
     */
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;

    /**
	 * 提交任务会获取线程的执行结果
     */
    <T> Future<T> submit(Callable<T> task);

    /**
	* 提交任务会获取线程的T类型的执行结果
     */
    <T> Future<T> submit(Runnable task, T result);

    /**
     * 提交任务
     */
    Future<?> submit(Runnable task);

    /**
     * 批量提交返回任务执行结果
     */
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

    /**
     * 批量提交返回任务执行结果
	 *增加了超时时间控制,这里的超时时间是针对的所有tasks,而不是单个task的超时时间。
	 *如果超时,会取消没有执行完的所有任务,并抛出超时异常
     */
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

    /**
     * 取得第一个方法的返回值,当第一个任务结束后,会调用interrupt方法中断其它任务
     */
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

    /**
     * 取得第一个方法的返回值,当第一个任务结束后,会调用interrupt方法中断其它任务
	 * 任务执行超时抛出异常
     */
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

Analyse des paramètres ThreadPoolExecutor

ThreadPoolExecutor est une interface utilisée pour traiter les tâches asynchrones. Il peut être compris comme un pool de threads et une file d'attente de tâches. Les tâches soumises à l'objet ExecutorService seront placées dans l'équipe de tâches ou directement exécutées par les threads du pool de threads. ThreadPoolExecutor prend en charge les ajustements Construisez des paramètres pour configurer différentes stratégies de traitement des tâches.
Le code du constructeur de pool de threads ThreadPoolExecutor est publié ci-dessous:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

La signification de chaque paramètre d'entrée de ThreadPoolExecutor: La relation entre les paramètres:

  1. Obtenez les threads disponibles à partir du pool de threads pour effectuer des tâches. S'il n'y a pas de threads disponibles, utilisez ThreadFactory pour créer de nouveaux threads jusqu'à ce que le nombre de threads atteigne la limite corePoolSize
  2. Une fois que le nombre de threads dans le pool de threads atteint corePoolSize, de nouvelles tâches seront placées dans la file d'attente jusqu'à ce que la file d'attente ne puisse plus accueillir plus de tâches
  3. Lorsque la file d'attente ne peut plus contenir plus de tâches, un nouveau thread sera créé jusqu'à ce que le nombre de threads atteigne la limite maxinumPoolSize
  4. Une fois que le nombre de threads atteint la limite maxinumPoolSize, la nouvelle tâche sera rejetée pour exécution et le RejectedExecutionHandler sera appelé pour le traitement

Stratégie de rejet RejectedExecutionHandler

1. AbortPolicy: lancer une exception directement, la stratégie par défaut;
2. CallerRunsPolicy: utiliser le thread de l'appelant pour exécuter la tâche;
3. DiscardOldestPolicy: supprimer la première tâche de la file d'attente de blocage et exécuter la tâche en cours;
4. DiscardPolicy: supprimer directement tâche;

Points clés de l'utilisation de ThreadPoolExecutor

  1. Configurez raisonnablement la taille du pool de threads: s'il s'agit d'une tâche gourmande en ressources processeur, le nombre de pools de threads peut être défini sur le nombre de cœurs de processeur + 1, s'il s'agit d'une tâche gourmande en E / S, le processeur sera plus inactif et le nombre de pools de threads doit être défini sur le nombre de cœurs de processeur * 2
  2. File d'attente de tâches: la file d'attente de tâches utilise généralement LinkedBlockingQueue pour spécifier la taille et est utilisée conjointement avec la stratégie de rejet. La valeur par défaut est Integer.Max_VALUE, qui est sujette à un dépassement de mémoire.
  3. Exception de tâche: ajoutez try {} catch {} pour attraper l'exception dans l'attention de la tâche dans le pool de threads, ce qui est pratique pour le dépannage

Pool de threads SpringBoot

org.springframework.core.task.TaskExecutor est la classe d'interface du pool de threads asynchrones Spring, et son essence est java.util.concurrent.Executor. Le pool de threads utilisé par défaut par springboot2.3.x est ThreadPoolTaskExecutor, qui peut être facilement ajusté via TaskExecutionProperties



SpringBoot définit son propre pool de threads asynchrones

@EnableAsync
@Component
@Slf4j
public class AsyncConfig implements AsyncConfigurer {
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        executor.setCorePoolSize(8);
        // 设置最大线程数
        executor.setMaxPoolSize(16);
        // 设置队列容量
        executor.setQueueCapacity(50);
        // 设置线程活跃时间(秒)
        executor.setKeepAliveSeconds(60);
        //设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
        executor.setAwaitTerminationSeconds(60);
        // 设置默认线程名称
        executor.setThreadNamePrefix("CodehomeAsyncTask-");
        // 设置拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.initialize();
        return executor;
    }
    public Executor getAsyncExecutor() {
        return taskExecutor();
    }
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new MyAsyncExceptionHandler();
    }

    /**
     * 自定义异常处理类
     */
    class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

        @Override
        public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
            log.info("Exception message - " + throwable.getMessage());
            log.info("Method name - " + method.getName());
            for (Object param : objects) {
                log.info("Parameter value - " + param);
            }
        }
    }
}

Deux façons d'utiliser


@Component
@Slf4j
public class UserServiceSyncTask {
	//不带返回值
    @Async
    public void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info(Thread.currentThread().getName());
    }
	//带返回值
    @Async
    public Future<String> echo(String msg){
        try {
            Thread.sleep(5000);
            return new AsyncResult<String>(Thread.currentThread().getName()+"hello world !!!!");
        } catch (InterruptedException e) {
            //
        }
        return null;
    }
}

Mille miles commence par un seul pas. Voici le dixième article de la série de tutoriels SpringBoot. Tous les codes sources du projet peuvent être téléchargés sur mon GitHub .

Je suppose que tu aimes

Origine blog.csdn.net/github_35592621/article/details/108248916
conseillé
Classement