传统的请求,只有一个线程去真实执行。高并发的时候就会容易造成接口堵塞!

测试串行机制弊端

@Service
public class AsyncServiceImpl implements AsyncService {

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

    @Override
    public void executeAsync() {
        logger.info("Start executeAsync");
        try {
            Thread.sleep(10_000);
            logger.info("当前线程名:"+Thread.currentThread().getName());
        } catch (Exception e) {
            logger.error("executeAsync exception");
        }
        logger.info("end executeAsync");
    }
}

同时发送2个请求,结果如下:串行执行

由上图得知,两个请求同时进来,线程exec-3正在执行期间,线程exec-9还没有真正拿到业务!当线程3执行完毕后,线程9才拿到业务开始执行!可想而知,这种串行机制,会非常耽误请求响应的时间!所以我们得让我们任务采用并行机制:使用多线程来实现!

Springboot整合 多线程 正文

看一下@Async注解的内容

将方法标记为异步执行候选的注解。也可以在类型级别使用,在这种情况下,所有类型的方法都被认为是异步的。但是请注意, @Async不支持在@Configuration类中声明的方法。
在目标方法签名方面,支持任何参数类型。但是,返回类型被限制为void或java.util.concurrent.Future 。在后一种情况下,您可以声明更具体的org.springframework.util.concurrent.ListenableFuture
或java.util.concurrent.CompletableFuture类型,它们允许与异步任务进行更丰富的交互并通过进一步的处理步骤进行即时组合。
从代理返回的Future句柄将是一个实际的异步Future ,可用于跟踪异步方法执行的结果。但是,由于目标方法需要实现相同的签名,因此它必须返回一个临时的Future句柄,该句柄只是传递一个值:
例如 Spring 的AsyncResult 、EJB 3.1 的javax.ejb.AsyncResult或java.util.concurrent.CompletableFuture.completedFuture(Object) 。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {


    指定异步操作的限定符值。
    可用于确定执行异步操作时要使用的目标执行器,匹配特定Executor或TaskExecutor bean     定义的限定符值(或 bean 名称)。
    当在类级别的@Async注释上指定时,表示给定的执行器应该用于类中的所有方法。 Async#value的方法级别使用始终覆盖在类级别设置的任何值
	String value() default "";

}

下面代码包含了线程池的4种 拒绝策略 RejectedExecutor 下面代码注释有说明

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author : zanglikun
 * @date : 2021/12/30 9:39
 * @Version: 1.0
 * @Desc : 线程池的配置
 * @Use : SpringBoot使用的时候 直接在实ServiceImpl下方法追加注解@Async("asyncServiceExecutor") 具体详情查看:https://www.zanglikun.com/8723.html
 */
@Component
@EnableAsync    // 开启异步,怕启动类遗忘了
@Slf4j
public class ExecutorConfig {
    // 定义一个线程池 方法名很重要!
    @Bean
    public Executor asyncServiceExecutor() {
        log.info("开始初始Bean:Executor:方法名:asyncServiceExecutor");
        ThreadPoolTaskExecutor threadPoolExecutor = new ThreadPoolTaskExecutor();
        // 配置设定核心线程数: 最小存在的线程数,如果当前线程小于队列数,依旧会创建线程!使得总线程数 >= 核心线程数
        threadPoolExecutor.setCorePoolSize(5);
        // 配置最大线程数
        threadPoolExecutor.setMaxPoolSize(10);
        // 配置非核心线程空闲线程存活时间 单位秒 默认是60秒
        threadPoolExecutor.setKeepAliveSeconds(60);
        // 允许运行线程数量为 0   这个一般不设置
        //threadPoolExecutor.setAllowCoreThreadTimeOut(true);
        // 配置待执行任务队列大小
        threadPoolExecutor.setQueueCapacity(10);
        // 配置线程池中线程的前缀名
        threadPoolExecutor.setThreadNamePrefix("Async-service-");

        // 配置线程满之后,拒绝策略
        /*
         AbortPolicy 这是默认策略。队列满了,是直接抛出 RejectedExecutionException 异常。
         CallerRunsPolicy 是阻塞新任务进入队列。如果当线程池的线程有空余,才回去读取队列任务执行,才会将阻塞任务加入队列。
         DiscardOldestPolicy 是丢弃队列中靠最前的任务。 如果队列有A、B、C 的3个阻塞,此时没有新的线程空闲,突然来了D线程,A线程就会被D代替。此时队列的排序是 B、C、D
         DiscardPolicy 是队列满了,直接丢弃新的任务。
         */
        threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        threadPoolExecutor.initialize();
        log.info("结束初始Bean:Executor:方法名:asyncServiceExecutor");
        return threadPoolExecutor;
    }
}

接下来 在ServiceImpl 的具体方法加入 注解@Async(”线程池的方法“)

    @Override
    @Async("asyncServiceExecutor")
    //@Async("这里填写的是你线程池的方法名,此处如果不写,会使用Spring默认的线程池")
    public void executeAsync() {
        logger.info("Start executeAsync");
        try {
            Thread.sleep(10000);
            logger.info("当前线程名:"+Thread.currentThread().getName());
        } catch (Exception e) {
            logger.error("executeAsync exception");
        }
        logger.info("end executeAsync");
    }

结果是:

2021-12-30 18:47:22.597  INFO 15728 --- [Async-service-1] c.x.z.s.serviceImpl.AsyncServiceImpl     : Start executeAsync
2021-12-30 18:47:22.841  INFO 15728 --- [Async-service-2] c.x.z.s.serviceImpl.AsyncServiceImpl     : Start executeAsync
2021-12-30 18:47:32.598  INFO 15728 --- [Async-service-1] c.x.z.s.serviceImpl.AsyncServiceImpl     : 当前线程名:Async-service-1
2021-12-30 18:47:32.598  INFO 15728 --- [Async-service-1] c.x.z.s.serviceImpl.AsyncServiceImpl     : end executeAsync
2021-12-30 18:47:32.842  INFO 15728 --- [Async-service-2] c.x.z.s.serviceImpl.AsyncServiceImpl     : 当前线程名:Async-service-2
2021-12-30 18:47:32.842  INFO 15728 --- [Async-service-2] c.x.z.s.serviceImpl.AsyncServiceImpl     : end executeAsync

Async-service-1 执行的时候 与 Async-service-2 执行时并行处理,不冲突、不阻塞!这就是线程池的异步执行某个犯法方法完结!

业务使用自己的线程池

    @Autowired
    @Qualifier(value = "DiyThreadPool")
    private Executor executor;

    @GetMapping("/testThreadPoolUse")
    @Operation(summary = "测试线城池使用", description = "测试线城池使用", tags = {"杂项API"})
    public void testThreadPoolUse() {
        for (int i = 0; i < 10; i++) {
            executor.execute(() -> {
                try {
                    Thread.sleep(1000);
                    log.info("我被{}执行了",Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
        }
    }

执行结果如下

2023-04-19 10:19:15.958|INFO |Async-service-3|com.jd.nt.icity.cgs.controller.SundryController:197|我被Async-service-3执行了
2023-04-19 10:19:15.958|INFO |Async-service-4|com.jd.nt.icity.cgs.controller.SundryController:197|我被Async-service-4执行了
2023-04-19 10:19:15.958|INFO |Async-service-2|com.jd.nt.icity.cgs.controller.SundryController:197|我被Async-service-2执行了
2023-04-19 10:19:15.961|INFO |Async-service-1|com.jd.nt.icity.cgs.controller.SundryController:197|我被Async-service-1执行了
2023-04-19 10:19:15.961|INFO |Async-service-5|com.jd.nt.icity.cgs.controller.SundryController:197|我被Async-service-5执行了

-- 这里我做个提示 上面是10点19分15秒,下面就是10点19分20秒了

2023-04-19 10:19:20.963|INFO |Async-service-4|com.jd.nt.icity.cgs.controller.SundryController:197|我被Async-service-4执行了
2023-04-19 10:19:20.963|INFO |Async-service-2|com.jd.nt.icity.cgs.controller.SundryController:197|我被Async-service-2执行了
2023-04-19 10:19:20.963|INFO |Async-service-3|com.jd.nt.icity.cgs.controller.SundryController:197|我被Async-service-3执行了
2023-04-19 10:19:20.964|INFO |Async-service-1|com.jd.nt.icity.cgs.controller.SundryController:197|我被Async-service-1执行了
2023-04-19 10:19:20.966|INFO |Async-service-5|com.jd.nt.icity.cgs.controller.SundryController:197|我被Async-service-5执行了
特殊说明:
上述文章均是作者实际操作后产出。烦请各位,请勿直接盗用!转载记得标注原文链接:www.zanglikun.com
第三方平台不会及时更新本文最新内容。如果发现本文资料不全,可访问本人的Java博客搜索:标题关键字。以获取全部资料 ❤