多线程与自动任务的一场悲剧的风花雪月

今天跟添加分享一个多线程与自动任务谈恋爱的悲惨故事,他们的结合,如果造化弄人,搞不好就是同归于尽。


一、相识

一次产品设计会上,促使了2个人”多线程“、”自动任务“的结合,本来是计划一起配合用手段调度资源造就稳定、丝滑、流畅的时代。可是造物主要是控制不好,就会是一场所有的所有陪葬的悲剧。

二、结合示例

1.多线程集成

多线程集成是前提,这里就不重复说了,见往期的博文。(下面整体代码示例也有说明)

2.自动任务集成

这里实现方式很多,也不多说,一般用springBoot的Scheduled就够用了,不需要额外引包。

三、整体代码示例

这里是我个人的一个小项目,不涉密,可以完全分享,下面就从多线程、自动任务整个整合分享一遍。

1.多线程配置项

#多线程配置
thread:
  config:
    corePoolSize: 5
    maxPoolSize: 15
    queueCapacity: 25

这里是减配的,需要详细配置、细粒度优化的,可以自行发挥。当然下面的配置类也需要优化增加初始设置。

2.多线程配置类


import com.xiaotian.datadiver.exception.MyRejectedExecutionHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

/**
 * 多线程配置类
 *
 * @author zhengwen
 */
@Configuration
@EnableAsync
@Slf4j
public class ThreadConfig implements AsyncConfigurer {
    
    

  /**
   * 核心线程数
   */
  @Value("${thread.config.corePoolSize:5}")
  private Integer corePoolSize;
  /**
   * 最大线程数量
   */
  @Value("${thread.config.maxPoolSize:15}")
  private Integer maxPoolSize;
  /**
   * 线程处理队列长度
   */
  @Value("${thread.config.queueCapacity:25}")
  private Integer queueCapacity;
  /**
   * 线程池维护线程所允许的空闲时间
   */
  @Value("${thread.config.keepAliveSeconds:300}")
  private Integer keepAliveSeconds;

  @Override
  public Executor getAsyncExecutor() {
    
    
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    log.info("---------[多线程配置初始化],核心线程数:{},最大线程数量:{},线程处理队列长度:{}", corePoolSize, maxPoolSize,
        queueCapacity);
    // 核心线程数
    executor.setCorePoolSize(corePoolSize);
    // 最大线程数量
    executor.setMaxPoolSize(maxPoolSize);
    // 线程处理队列长度
    executor.setQueueCapacity(queueCapacity);
    // 线程空闲时间
    executor.setKeepAliveSeconds(keepAliveSeconds);
    //当调度器shutdown被调用时等待当前被调度的任务完成
    executor.setWaitForTasksToCompleteOnShutdown(false);
    //由调用线程处理该任务
    //executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    //自定义线程池饱和机制
    executor.setRejectedExecutionHandler(new MyRejectedExecutionHandler());
    executor.initialize();
    return executor;
  }

  @Override
  public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    
    
    log.info("-----------多线程异常handler------------");
    return (throwable, method, objects) -> {
    
    
      log.error("Asyn返回异常:", throwable);
      if (method != null && method.getName() != null) {
    
    
        log.error("Asyn返回异常方法:{}", method.getName());
      }
    };
  }
}

3.自动任务配置项

#自带schedule自动任务调度开关配置
scheduling:
  enabled: true
  business:
    licenseCheck:
      cron: 0 50 8 * * ?
    masterSlaveRun:
      fixedDelay: 3000

这其实就是一个开关,然后是2个自定义的业务配置cron表达式。

4.自动任务配置业务类


import cn.hutool.core.date.DateUtil;
import com.xiaotian.datadiver.service.ThreadService;
import com.xiaotian.datadiver.util.LicenseUtil;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

/**
 * 业务自动任务
 *
 * @author zhengwen
 **/
@Configuration
@EnableScheduling
@ConditionalOnProperty(prefix = "scheduling", name = "enabled", havingValue = "true")
@Slf4j
public class BusinessTask {
    
    

  @Resource
  private ThreadService threadService;

  /**
   * 校验授权签名
   */
  @Scheduled(cron = "${scheduling.business.licenseCheck.cron}")
  public void licenseCheck() {
    
    
    log.info("--校验授权签名校验--");
    boolean licensePass = LicenseUtil.checkSoftLicense();
    if (licensePass) {
    
    
      log.info("--签名校验通过--");
    } else {
    
    
      log.info("--签名校验不通过,请联系管理员--");
      //关闭服务
      Runtime.getRuntime().exit(500);
    }
  }

  /**
   * 多线程-主从线程与周期测试
   */
  @Scheduled(fixedDelayString = "${scheduling.business.masterSlaveRun.fixedDelay}")
  public void masterSlaveThreadRunTest() {
    
    
    log.info("--多线程-主从线程与周期测试--");
    String now = DateUtil.now();
    //执行完成后间隔3秒,设置睡5秒钟
    long sleepTime = 5000;
    threadService.slaveThreadRun(now, sleepTime);
    log.info("--主线程运行完成,时间:{}", now);
  }
}

注意这个类上的注解,分别是配置扫描注解、开启自动任务注解、配置文件读取注解。

5.service类


import java.util.concurrent.Future;

/**
 * @author zhengwen
 **/
public interface ThreadService {
    
    

  /**
   * 从线程方法
   * @param now
   * @param sleepTime
   */
  void slaveThreadRun(String now,long sleepTime);

  /**
   *
   * @param now
   * @param sleepTime
   * @return
   */
  Future<?> slaveThreadRunHasResult(String now,long sleepTime);
}

这里定义了2个接口,分别是

  1. 从线程业务方法
  2. 带返回值的从线程业务方法(这里先买个关子,用这个讲述他们的故事更明显一点,下期分享)

6.service实现类


import com.xiaotian.datadiver.service.ThreadService;
import java.util.concurrent.Future;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

/**
 * @author zhengwen
 **/
@Slf4j
@Service
public class ThreadServiceImpl implements ThreadService {
    
    

  @Async
  @Override
  public void slaveThreadRun(String now, long sleepTime) {
    
    
    //主线程是1分钟执行一次,这里我就让睡1分半钟
    try {
    
    
      Thread.sleep(sleepTime);
    } catch (InterruptedException e) {
    
    
      log.error("----从线程异常:{}", e.getMessage(), e);
    }
    log.info("我{}睡了{},醒了,可以继续了", now, sleepTime);


  }

  @Override
  public Future<?> slaveThreadRunHasResult(String now, long sleepTime) {
    
    

    return null;
  }
}

注意实现方法上的@Async注解,否在开启线程无效
到此代码就分享完了。

四、运行结果

在这里插入图片描述

代码分析:
首先分清主从:
主线程:自动任务方法(masterSlaveThreadRunTest)
从线程:自动任务里调用的业务方法(slaveThreadRun)
结果分析:
图上已经标明了交叉打印,大家仔细想想我自动任务设置的是希望这样吗?
自动任务设置解释:
我设置的是希望自动任务执行一次完成后,间隔3s再进行下一次,对吧?
@Scheduled(fixedDelayString = 完成后延迟毫秒数)
在这里插入图片描述
需要详细理解Scheduled的8个配置,可以自行百度。
悲剧起源:
试想一下,我想的是一次执行完成后,间隔10s再执行,而业务方法一次执行完成需要执行60s,那将会是什么情况?
这里就是主线程间隔3s执行一次,而从线程执行一次需要5s。这里显然主线程不会认为从线程5s执行完了再过3s再执行下一次,如果是,那这里应该是顺序打印,对吧?
悲剧发生背景:
如果这2个时间相差巨大,随着时间的推移,你们说会发生啥?
悲剧结果猜想:
服务器的线程资源够他们这样组合调度吗?


总结

多线程固然好用,自动任务固然自动。
当他们决定在一起,一定要注意他们的”脚步差”,否在,渐行渐远,心生怨念,拉整个服务器陪葬。也许有人会说,不是加了自动任务的抛弃策略吗?可是要知道,多线程本就不应该一直被占满而等抛弃,那个时候服务器已经可能都忙不过来做抛弃了,何来稳定、丝滑、流畅?

猜你喜欢

转载自blog.csdn.net/zwrlj527/article/details/123557401