【踩坑日记】我的@Scheduled 怎么不运行了?

背景

近期开发着定制项目,因为并发要求较低但业务繁杂,需要多个定时任务执行数据和业务。

然后呢就在那么一个光和日丽,微风徐徐的下午

它就那样不运行了

原因

怎么就不运行了呢?怎么就不运行了呢?怎么就不运行了呢?

我也不知道呀?

来回忆一下怎么创建一个定时任务

  1. 启动Spring的定时任务能力@EnableScheduling
  2. 在被Spring管理的类中,在要定时执行的方法上加上@Scheduled(xxx)

第一步是加上@EnableScheduling,这个注解干了什么呢

@EnableScheduling

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {

}

@EnableScheduling注解Import了SchedulingConfiguration类

java 复制代码
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {

    @Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
       return new ScheduledAnnotationBeanPostProcessor();
    }

}

SchedulingConfiguration类创建了个一个ScheduledAnnotationBeanPostProcessor类型,名为org.springframework.context.annotation.internalScheduledAnnotationProcessor的Bean

java 复制代码
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {

    @Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
       return new ScheduledAnnotationBeanPostProcessor();
    }

}
java 复制代码
public abstract class TaskManagementConfigUtils {

    /**
     * The bean name of the internally managed Scheduled annotation processor.
     */
    public static final String SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME =
          "org.springframework.context.annotation.internalScheduledAnnotationProcessor";
      ......

来看看ScheduledAnnotationBeanPostProcessor的源码

java 复制代码
public class ScheduledAnnotationBeanPostProcessor
       implements ScheduledTaskHolder, MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,
       Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,
       SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {
       ......

ScheduledAnnotationBeanPostProcessor实现了spring相关生命周期的处理器、容器以及事件处理器,他的注解上说明了这个处理器用于注册注解了@Scheduled的方法,来看看他的#postProcessAfterInitialization方法

java 复制代码
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
    if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
          bean instanceof ScheduledExecutorService) {
       // Ignore AOP infrastructure such as scoped proxies.
       return bean;
    }

    Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
    if (!this.nonAnnotatedClasses.contains(targetClass)) {
       Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
             (MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
                Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
                      method, Scheduled.class, Schedules.class);
                return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
             });
       if (annotatedMethods.isEmpty()) {
          this.nonAnnotatedClasses.add(targetClass);
          if (logger.isTraceEnabled()) {
             logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
          }
       }
       else {
          // Non-empty set of methods
          annotatedMethods.forEach((method, scheduledMethods) ->
                scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
          if (logger.isTraceEnabled()) {
             logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
                   "': " + annotatedMethods);
          }
       }
    }
    return bean;
}

这个方法找到所有注解了@Scheduled的方法,然后遍历调用processScheduled,来看看他的处理方法#processScheduled

java 复制代码
/**
 * Process the given {@code @Scheduled} method declaration on the given bean.
 * @param scheduled the @Scheduled annotation
 * @param method the method that the annotation has been declared on
 * @param bean the target bean instance
 * @see #createRunnable(Object, Method)
 */
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
    try {
       Runnable runnable = createRunnable(bean, method);
       boolean processedSchedule = false;
       String errorMessage =
             "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";

       Set<ScheduledTask> tasks = new LinkedHashSet<>(4);

       // Determine initial delay
       long initialDelay = scheduled.initialDelay();
       String initialDelayString = scheduled.initialDelayString();
       if (StringUtils.hasText(initialDelayString)) {
          Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
          if (this.embeddedValueResolver != null) {
             initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
          }
          if (StringUtils.hasLength(initialDelayString)) {
             try {
                initialDelay = parseDelayAsLong(initialDelayString);
             }
             catch (RuntimeException ex) {
                throw new IllegalArgumentException(
                      "Invalid initialDelayString value "" + initialDelayString + "" - cannot parse into long");
             }
          }
       }

       // Check cron expression
       String cron = scheduled.cron();
       if (StringUtils.hasText(cron)) {
          String zone = scheduled.zone();
          if (this.embeddedValueResolver != null) {
             cron = this.embeddedValueResolver.resolveStringValue(cron);
             zone = this.embeddedValueResolver.resolveStringValue(zone);
          }
          if (StringUtils.hasLength(cron)) {
             Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
             processedSchedule = true;
             if (!Scheduled.CRON_DISABLED.equals(cron)) {
                TimeZone timeZone;
                if (StringUtils.hasText(zone)) {
                   timeZone = StringUtils.parseTimeZoneString(zone);
                }
                else {
                   timeZone = TimeZone.getDefault();
                }
                tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
             }
          }
       }

       // At this point we don't need to differentiate between initial delay set or not anymore
       if (initialDelay < 0) {
          initialDelay = 0;
       }

       // Check fixed delay
       long fixedDelay = scheduled.fixedDelay();
       if (fixedDelay >= 0) {
          Assert.isTrue(!processedSchedule, errorMessage);
          processedSchedule = true;
          tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
       }
       String fixedDelayString = scheduled.fixedDelayString();
       if (StringUtils.hasText(fixedDelayString)) {
          if (this.embeddedValueResolver != null) {
             fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
          }
          if (StringUtils.hasLength(fixedDelayString)) {
             Assert.isTrue(!processedSchedule, errorMessage);
             processedSchedule = true;
             try {
                fixedDelay = parseDelayAsLong(fixedDelayString);
             }
             catch (RuntimeException ex) {
                throw new IllegalArgumentException(
                      "Invalid fixedDelayString value "" + fixedDelayString + "" - cannot parse into long");
             }
             tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
          }
       }

       // Check fixed rate
       long fixedRate = scheduled.fixedRate();
       if (fixedRate >= 0) {
          Assert.isTrue(!processedSchedule, errorMessage);
          processedSchedule = true;
          tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
       }
       String fixedRateString = scheduled.fixedRateString();
       if (StringUtils.hasText(fixedRateString)) {
          if (this.embeddedValueResolver != null) {
             fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
          }
          if (StringUtils.hasLength(fixedRateString)) {
             Assert.isTrue(!processedSchedule, errorMessage);
             processedSchedule = true;
             try {
                fixedRate = parseDelayAsLong(fixedRateString);
             }
             catch (RuntimeException ex) {
                throw new IllegalArgumentException(
                      "Invalid fixedRateString value "" + fixedRateString + "" - cannot parse into long");
             }
             tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
          }
       }

       // Check whether we had any attribute set
       Assert.isTrue(processedSchedule, errorMessage);

       // Finally register the scheduled tasks
       synchronized (this.scheduledTasks) {
          Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
          regTasks.addAll(tasks);
       }
    }
    catch (IllegalArgumentException ex) {
       throw new IllegalStateException(
             "Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
    }
}

在了解处理过程之前,先来看看Scheduled有什么参数

java 复制代码
public @interface Scheduled {

    /**
     * A special cron expression value that indicates a disabled trigger: {@value}.
     * <p>This is primarily meant for use with ${...} placeholders, allowing for
     * external disabling of corresponding scheduled methods.
     * @since 5.1
     */
    String CRON_DISABLED = "-";


    /**
     * A cron-like expression, extending the usual UN*X definition to include triggers
     * on the second as well as minute, hour, day of month, month and day of week.
     * <p>E.g. {@code "0 * * * * MON-FRI"} means once per minute on weekdays
     * (at the top of the minute - the 0th second).
     * <p>The special value {@link #CRON_DISABLED "-"} indicates a disabled cron trigger,
     * primarily meant for externally specified values resolved by a ${...} placeholder.
     * @return an expression that can be parsed to a cron schedule
     * @see org.springframework.scheduling.support.CronSequenceGenerator
     */
    String cron() default "";

    /**
     * A time zone for which the cron expression will be resolved. By default, this
     * attribute is the empty String (i.e. the server's local time zone will be used).
     * @return a zone id accepted by {@link java.util.TimeZone#getTimeZone(String)},
     * or an empty String to indicate the server's default time zone
     * @since 4.0
     * @see org.springframework.scheduling.support.CronTrigger#CronTrigger(String, java.util.TimeZone)
     * @see java.util.TimeZone
     */
    String zone() default "";

    /**
     * Execute the annotated method with a fixed period in milliseconds between the
     * end of the last invocation and the start of the next.
     * @return the delay in milliseconds
     */
    long fixedDelay() default -1;

    /**
     * Execute the annotated method with a fixed period in milliseconds between the
     * end of the last invocation and the start of the next.
     * @return the delay in milliseconds as a String value, e.g. a placeholder
     * or a {@link java.time.Duration#parse java.time.Duration} compliant value
     * @since 3.2.2
     */
    String fixedDelayString() default "";

    /**
     * Execute the annotated method with a fixed period in milliseconds between
     * invocations.
     * @return the period in milliseconds
     */
    long fixedRate() default -1;

    /**
     * Execute the annotated method with a fixed period in milliseconds between
     * invocations.
     * @return the period in milliseconds as a String value, e.g. a placeholder
     * or a {@link java.time.Duration#parse java.time.Duration} compliant value
     * @since 3.2.2
     */
    String fixedRateString() default "";

    /**
     * Number of milliseconds to delay before the first execution of a
     * {@link #fixedRate()} or {@link #fixedDelay()} task.
     * @return the initial delay in milliseconds
     * @since 3.2
     */
    long initialDelay() default -1;

    /**
     * Number of milliseconds to delay before the first execution of a
     * {@link #fixedRate()} or {@link #fixedDelay()} task.
     * @return the initial delay in milliseconds as a String value, e.g. a placeholder
     * or a {@link java.time.Duration#parse java.time.Duration} compliant value
     * @since 3.2.2
     */
    String initialDelayString() default "";

}

@Scheduled提供了

  • cron
  • zone
  • fixedDelay、fixedDelayString
  • fixedRate、fixedRateString
  • initialDelay、initialDelayString

processScheduled的执行过程如下:

  1. 根据@Scheduled注解的参数构建任务
  2. 根据对应的任务类型注册成ScheduledTask
  3. 将任务加入到scheduledTasks任务列表

细心且多智的我们肯定还记得ScheduledAnnotationBeanPostProcessor 还实现了ApplicationListenerSmartInitializingSingleton

java 复制代码
@Override
public void afterSingletonsInstantiated() {
    // Remove resolved singleton classes from cache
    this.nonAnnotatedClasses.clear();

    if (this.applicationContext == null) {
       // Not running in an ApplicationContext -> register tasks early...
       finishRegistration();
    }
}

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
    if (event.getApplicationContext() == this.applicationContext) {
       // Running in an ApplicationContext -> register tasks this late...
       // giving other ContextRefreshedEvent listeners a chance to perform
       // their work at the same time (e.g. Spring Batch's job registration).
       finishRegistration();
    }
}

二者都调用了私有方法#finishRegistration,这个方法是完成注册,将任务队列注册到Spring容器中交由Spring管理

到这里 定时任务知道怎么来的了,但是这和他不运行没啥关系啊?

在Spring依赖包中,我们可以看到这个interface TaskScheduler

java 复制代码
/**
 * Task scheduler interface that abstracts the scheduling of
 * {@link Runnable Runnables} based on different kinds of triggers.
 *
 * <p>This interface is separate from {@link SchedulingTaskExecutor} since it
 * usually represents for a different kind of backend, i.e. a thread pool with
 * different characteristics and capabilities. Implementations may implement
 * both interfaces if they can handle both kinds of execution characteristics.
 *
 * <p>The 'default' implementation is
 * {@link org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler},
 * wrapping a native {@link java.util.concurrent.ScheduledExecutorService}
 * and adding extended trigger capabilities.
 *
 * <p>This interface is roughly equivalent to a JSR-236
 * {@code ManagedScheduledExecutorService} as supported in Java EE 7
 * environments but aligned with Spring's {@code TaskExecutor} model.
 *
 * @author Juergen Hoeller
 * @since 3.0
 * @see org.springframework.core.task.TaskExecutor
 * @see java.util.concurrent.ScheduledExecutorService
 * @see org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
 */
public interface TaskScheduler {
        /**
         * Schedule the given {@link Runnable}, invoking it whenever the trigger
         * indicates a next execution time.
         * <p>Execution will end once the scheduler shuts down or the returned
         * {@link ScheduledFuture} gets cancelled.
         * @param task the Runnable to execute whenever the trigger fires
         * @param trigger an implementation of the {@link Trigger} interface,
         * e.g. a {@link org.springframework.scheduling.support.CronTrigger} object
         * wrapping a cron expression
         * @return a {@link ScheduledFuture} representing pending completion of the task,
         * or {@code null} if the given Trigger object never fires (i.e. returns
         * {@code null} from {@link Trigger#nextExecutionTime})
         * @throws org.springframework.core.task.TaskRejectedException if the given task was not accepted
         * for internal reasons (e.g. a pool overload handling policy or a pool shutdown in progress)
         * @see org.springframework.scheduling.support.CronTrigger
         */
        @Nullable
        ScheduledFuture<?> schedule(Runnable task, Trigger trigger);
        ......

根据注解所描述的内容说明我们可以知道,这就是定时任务执行的接口,来看看schedule方法的实现

先来看看ConcurrentTaskScheduler的实现

kotlin 复制代码
@Override
@Nullable
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
    try {
       if (this.enterpriseConcurrentScheduler) {
          return new EnterpriseConcurrentTriggerScheduler().schedule(decorateTask(task, true), trigger);
       }
       else {
          ErrorHandler errorHandler =
                (this.errorHandler != null ? this.errorHandler : TaskUtils.getDefaultErrorHandler(true));
          return new ReschedulingRunnable(task, trigger, this.scheduledExecutor, errorHandler).schedule();
       }
    }
    catch (RejectedExecutionException ex) {
       throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
    }
}

查看ConcurrentTaskScheduler的源码,我们可以看到他的默认构造方法和初始化方法如下

kotlin 复制代码
public ConcurrentTaskScheduler() {
    super();
    this.scheduledExecutor = initScheduledExecutor(null);
}

private ScheduledExecutorService initScheduledExecutor(@Nullable ScheduledExecutorService scheduledExecutor) {
    if (scheduledExecutor != null) {
       this.scheduledExecutor = scheduledExecutor;
       this.enterpriseConcurrentScheduler = (managedScheduledExecutorServiceClass != null &&
             managedScheduledExecutorServiceClass.isInstance(scheduledExecutor));
    }
    else {
       this.scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
       this.enterpriseConcurrentScheduler = false;
    }
    return this.scheduledExecutor;
}

可以看到他默认是个单线程的任务提交池,再来看看ThreadPoolTaskScheduler实现的schedule方法

java 复制代码
// TaskScheduler implementation

@Override
@Nullable
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
    ScheduledExecutorService executor = getScheduledExecutor();
    try {
       ErrorHandler errorHandler = this.errorHandler;
       if (errorHandler == null) {
          errorHandler = TaskUtils.getDefaultErrorHandler(true);
       }
       return new ReschedulingRunnable(task, trigger, executor, errorHandler).schedule();
    }
    catch (RejectedExecutionException ex) {
       throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
    }
}

从源码可以知道,获取ScheduledExecutorService线程池而后提交执行,来看看他的初始化方法

kotlin 复制代码
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);
}
kotlin 复制代码
@Override
protected ExecutorService initializeExecutor(
       ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {

    this.scheduledExecutor = createExecutor(this.poolSize, threadFactory, rejectedExecutionHandler);

    if (this.removeOnCancelPolicy) {
       if (this.scheduledExecutor instanceof ScheduledThreadPoolExecutor) {
          ((ScheduledThreadPoolExecutor) this.scheduledExecutor).setRemoveOnCancelPolicy(true);
       }
       else {
          logger.debug("Could not apply remove-on-cancel policy - not a ScheduledThreadPoolExecutor");
       }
    }

    return this.scheduledExecutor;
}

这里的poolSize在spring的自动配置bean的时候进行设置,代码里的默认值是1

java 复制代码
@ConditionalOnClass(ThreadPoolTaskScheduler.class)
@Configuration
@EnableConfigurationProperties(TaskSchedulingProperties.class)
@AutoConfigureAfter(TaskExecutionAutoConfiguration.class)
public class TaskSchedulingAutoConfiguration {

    @Bean
    @ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
    @ConditionalOnMissingBean({ SchedulingConfigurer.class, TaskScheduler.class, ScheduledExecutorService.class })
    public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) {
       return builder.build();
    }

    @Bean
    @ConditionalOnMissingBean
    public TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties,
          ObjectProvider<TaskSchedulerCustomizer> taskSchedulerCustomizers) {
       TaskSchedulerBuilder builder = new TaskSchedulerBuilder();
       builder = builder.poolSize(properties.getPool().getSize());
       builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
       builder = builder.customizers(taskSchedulerCustomizers);
       return builder;
    }

}
java 复制代码
@ConfigurationProperties("spring.task.scheduling")
public class TaskSchedulingProperties {

    private final Pool pool = new Pool();
    
    

public static class Pool {

    /**
     * Maximum allowed number of threads.
     */
    private int size = 1;

    public int getSize() {
       return this.size;
    }

    public void setSize(int size) {
       this.size = size;
    }

}

这时候仿佛我们看到了希望,是否执行线程不足以执行定时任务?

我们先来确定默认的是哪个任务调度器,在org.springframework.boot.autoconfigure.task包下有个TaskSchedulingAutoConfiguration对象

java 复制代码
@ConditionalOnClass(ThreadPoolTaskScheduler.class)
@Configuration
@EnableConfigurationProperties(TaskSchedulingProperties.class)
@AutoConfigureAfter(TaskExecutionAutoConfiguration.class)
public class TaskSchedulingAutoConfiguration {

    @Bean
    @ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
    @ConditionalOnMissingBean({ SchedulingConfigurer.class, TaskScheduler.class, ScheduledExecutorService.class })
    public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) {
       return builder.build();
    }

    @Bean
    @ConditionalOnMissingBean
    public TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties,
          ObjectProvider<TaskSchedulerCustomizer> taskSchedulerCustomizers) {
       TaskSchedulerBuilder builder = new TaskSchedulerBuilder();
       builder = builder.poolSize(properties.getPool().getSize());
       builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
       builder = builder.customizers(taskSchedulerCustomizers);
       return builder;
    }

}

按照这个发现,我们给spring.task.scheduling.pool.size配上一个适合的值,应该就成了吧

可是呢

可是呢

可是呢

宿主机的线程怎么炸了啊!!!

查看执行任务发现有存在长期hold线程的任务,任务执行线程没上限?

org.springframework.boot.autoconfigure.task包下还有个TaskExecutionAutoConfiguration对象

Execution这个词我们就知道了他是配置任务执行线程池的配置

来看看他的配置参数是什么

java 复制代码
@ConfigurationProperties("spring.task.execution")
public class TaskExecutionProperties {

    private final Pool pool = new Pool();

    ......

    public static class Pool {

        /**
         * Queue capacity. An unbounded capacity does not increase the pool and therefore
         * ignores the "max-size" property.
         */
        private int queueCapacity = Integer.MAX_VALUE;

        /**
         * Core number of threads.
         */
        private int coreSize = 8;

        /**
         * Maximum allowed number of threads. If tasks are filling up the queue, the pool
         * can expand up to that size to accommodate the load. Ignored if the queue is
         * unbounded.
         */
        private int maxSize = Integer.MAX_VALUE;

        /**
         * Whether core threads are allowed to time out. This enables dynamic growing and
         * shrinking of the pool.
         */
        private boolean allowCoreThreadTimeout = true;

        /**
         * Time limit for which threads may remain idle before being terminated.
         */
        private Duration keepAlive = Duration.ofSeconds(60);
        ......

他是个核心线程只有8最大线程数是int最大值的线程池

察看java应用的stack可以发现,确实很多都是task-开头的线程任务,马上修改 spring.task.execution.pool.maxSizespring.task.execution.pool.queueCapacity的值

把hold住线程的任务中的代码缺陷修复

果然这就解决了,恢复正常了

相关推荐
raoxiaoya5 小时前
同时安装多个版本的golang
开发语言·后端·golang
考虑考虑7 小时前
go使用gorilla/websocket实现websocket
后端·程序员·go
李少兄7 小时前
解决Spring Boot多模块自动配置失效问题
java·spring boot·后端
Piper蛋窝8 小时前
Go 1.19 相比 Go 1.18 有哪些值得注意的改动?
后端
码农BookSea8 小时前
不用Mockito写单元测试?你可能在浪费一半时间
后端·单元测试
他҈姓҈林҈8 小时前
Spring Boot 支持政策
spring boot
codingandsleeping9 小时前
Express入门
javascript·后端·node.js
ss2739 小时前
基于Springboot + vue + 爬虫实现的高考志愿智能推荐系统
spring boot·后端·高考
两点王爷10 小时前
springboot项目文件上传到服务器本机,返回访问地址
java·服务器·spring boot·文件上传
专注API从业者10 小时前
《Go 语言高并发爬虫开发:淘宝商品 API 实时采集与 ETL 数据处理管道》
开发语言·后端·爬虫·golang