8.9.3-多线程(jdk和spring线程池的配置及使用)

5 篇文章 0 订阅


源代码:https://gitee.com/kelvin11/multithreading.git
内容提要:

  1. jdk ThreadPoolExecutor参数构建线程池
    1. 线程池配置参数说明:corePoolSize、maxPoolSize、queue、RejectExecutionHandler拒绝策略。何时填充queue
    2. 提交callable任务,通过future获取结果。
    3. 提交callable任务,使用stream().parallel()并行获取结果
  2. spring ThreadPoolTaskExecutor线程池配置
    1. 注意:异步方法不能与被调用的异步方法在同一个类中,否则将不使用多线程
    2. CallerRunsPolicy 由该线程调用线程运行。直接调用Runnable的run方法运行(即,当超过maxPoolSize之后,提交的请求在利用完主线程之后,会阻塞,不抛异常)
    3. 手动注入ThreadPoolTaskExecutor,提交callable任务,通过future获取结果

1. jdk线程池

通过idea的diagram总览jdk线程池,层次结构比较简单,我们使用ExecutorService作为接口,ThreadPoolExecutor作为实现类来创建线程池。

要注意的是:推荐自己去new ThreadPoolExecutor,而不是使用Executors.xxx来创建,后者,没有提供一些参数配置。然后配置是比较重要的,对于使用线程池也比较有用,否则容易出现一些不容易理解的错误。

创建的demo:

ExecutorService executorService = new ThreadPoolExecutor(2,
                2,
                60L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(2),
                new ThreadPoolExecutor.DiscardPolicy());

不管是jdk的ThreadPoolExecutor还是Spring的ThreadPoolTaskExecutor,对于创建线程的过程都是一样的:

当往线程池中提交新任务时,线程池主要流程如下:
核心线程数 -> 线程队列 -> 最大线程数 -> 拒绝策略
a. 如果池中任务数 < corePoolSize (核心线程数),创建新线程立即执行任务
b. 如果池中任务数 > corePoolSize,新任务放到缓存队列当中等待执行
c. 队列满,线程数量<maxPoolSize,新建线程立即执行任务
d. 队列满,线程数量>=maxPoolSize,使用拒绝策略拒绝

1.1. 超过maxPoolSize后直接丢弃策略

@Test
    public void testJdkExecutorServiceRunnable() throws InterruptedException {

        ExecutorService executorService = new ThreadPoolExecutor(2,
                2,
                60L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(2),
                new ThreadPoolExecutor.DiscardPolicy());

        for (int i = 0; i < 10; i++) {
            int finalI = i;
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    log.info("hello" + finalI);
                }
            });
        }

        Thread.sleep(5000);
    }

结果说明:

普通的Runnable线程
1 jdk线程池,ExecutorService接口
2. new ThreadPoolExecutor方式创建
3. ThreadPoolExecutor.DiscardPolicy超过 (核心线程数 + 队列数)之后,会被默默的丢弃,不会抛异常,也不会打印日志。
运行结果:
15:35:06.880 [pool-1-thread-2] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - hello1
15:35:06.880 [pool-1-thread-1] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - hello0
15:35:08.888 [pool-1-thread-2] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - hello2
15:35:08.888 [pool-1-thread-1] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - hello3

1.2. 提交Callable对象,可以获取结果

1.2.1. 非并行获取结果

@Test
    public void testJdkExecutorServiceCallable() throws InterruptedException {

        ExecutorService executorService = new ThreadPoolExecutor(10,
                20,
                60L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(2),
                new ThreadPoolExecutor.DiscardPolicy());

        List<Future<String>> futureList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Future<String> future = executorService.submit(new MyCallable().setName(String.valueOf(i)));
            futureList.add(future);
        }
        // 获取结果(注意这里没有使用并行流,所以,如果在获取某个结果的时候等待时间较长,可能其他线程的获取结果瞬间就会拿到,看看执行结果就会明白)
        futureList.stream().forEach(e -> {
            try {
                log.info("执行结果[{}]:", new Object[]{e.get()});
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            } catch (ExecutionException ex) {
                ex.printStackTrace();
            }
        });

        Thread.sleep(100 * 1000);
    }

执行结果1:

运行结果1:
16:16:19.662 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[0]睡了:9秒]:
16:16:19.668 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[1]睡了:0秒]:
16:16:19.669 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[2]睡了:3秒]:
16:16:19.669 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[3]睡了:1秒]:
16:16:19.669 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[4]睡了:4秒]:
16:16:19.669 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[5]睡了:1秒]:
16:16:19.669 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[6]睡了:4秒]:
16:16:19.669 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[7]睡了:3秒]:
16:16:19.669 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[8]睡了:8秒]:
16:16:19.669 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[9]睡了:7秒]:
分析:
由于获取第1个线程结果的时候,第1个线程睡了9s,导致后面都已经有结果不用等待,所以瞬间在16:16:19就可以拿到所有的结果。

执行结果2:

运行结果2:
16:17:04.278 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[0]睡了:4秒]:
16:17:04.283 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[1]睡了:0秒]:
16:17:04.283 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[2]睡了:0秒]:
16:17:04.283 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[3]睡了:1秒]:
16:17:08.278 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[4]睡了:8秒]:
16:17:08.279 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[5]睡了:6秒]:
16:17:08.279 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[6]睡了:6秒]:
16:17:08.279 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[7]睡了:8秒]:
16:17:09.274 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[8]睡了:9秒]:
16:17:09.274 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[9]睡了:9秒]:
分析:
第1个线程睡了4s,所以导致前4个线程是一起拿到结果的。
第5个线程睡了8s,所以5-8个线程是一起拿到结果的
最后2个线程,都是睡了9s,所以是一起拿到结果。

1.2.2. 并行获取结果

@Test
    public void testJdkExecutorServiceCallableStreamParallel() throws InterruptedException {

        ExecutorService executorService = new ThreadPoolExecutor(10,
                20,
                60L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(2),
                new ThreadPoolExecutor.DiscardPolicy());

        List<Future<String>> futureList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Future<String> future = executorService.submit(new MyCallable().setName(String.valueOf(i)));
            futureList.add(future);
        }
        // 获取结果(与上一个案例不同的,就是使用了parallel()方法,并行获取结果。)
        futureList.stream().parallel().forEach(e -> {
            try {
                log.info("执行结果[{}]:", new Object[]{e.get()});
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            } catch (ExecutionException ex) {
                ex.printStackTrace();
            }
        });

        Thread.sleep(100 * 1000);
    }

执行结果1:

执行结果1比较理想,都是睡的短的先拿到结果:
17:29:14.887 [ForkJoinPool.commonPool-worker-1] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[2]睡了:3秒]:
17:29:14.887 [ForkJoinPool.commonPool-worker-6] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[0]睡了:3秒]:
17:29:15.883 [ForkJoinPool.commonPool-worker-6] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[5]睡了:4秒]:
17:29:15.883 [ForkJoinPool.commonPool-worker-7] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[9]睡了:4秒]:
17:29:17.883 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[6]睡了:6秒]:
17:29:18.880 [ForkJoinPool.commonPool-worker-3] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[1]睡了:7秒]:
17:29:19.883 [ForkJoinPool.commonPool-worker-4] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[7]睡了:8秒]:
17:29:19.883 [ForkJoinPool.commonPool-worker-2] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[8]睡了:8秒]:
17:29:20.882 [ForkJoinPool.commonPool-worker-5] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[4]睡了:9秒]:
17:29:20.882 [ForkJoinPool.commonPool-worker-1] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[3]睡了:9秒]:

执行结果2:

运行结果2(睡了0s的,等了3s后才输出,这个应该是跟stream的parallel概念相关,后面专题看stream的时候再整理):
17:32:21.578 [ForkJoinPool.commonPool-worker-4] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[7]睡了:3秒]:
17:32:21.584 [ForkJoinPool.commonPool-worker-4] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[5]睡了:0秒]:
17:32:21.584 [ForkJoinPool.commonPool-worker-4] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[3]睡了:1秒]:
17:32:23.571 [ForkJoinPool.commonPool-worker-7] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[4]睡了:5秒]:
17:32:23.571 [ForkJoinPool.commonPool-worker-6] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[0]睡了:5秒]:
17:32:23.571 [ForkJoinPool.commonPool-worker-1] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[2]睡了:5秒]:
17:32:24.575 [ForkJoinPool.commonPool-worker-3] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[1]睡了:6秒]:
17:32:25.574 [ForkJoinPool.commonPool-worker-5] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[9]睡了:7秒]:
17:32:26.570 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[6]睡了:8秒]:
17:32:27.572 [ForkJoinPool.commonPool-worker-2] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[8]睡了:9秒]:
结论是:不一定使用了parallel()就一定会按照睡的时间由短到长得到结果。

2. Spring线程池

2.1. 概览

下图是spring的scheduling包关于ThreadPoolTaskExecutor的概览。划线的,就是我们要关注的重点。

直接上代码,边写边看边理解。

2.2. 配置线程池

package com.example.simple.spring_scheduling;

import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import lombok.Data;

@Configuration
public class ThreadPoolConfig {

    ThreadPoolProperties properties = new ThreadPoolProperties();

    /**
     * 可以通过名称注入这个配置好的线程池。
     * @return
     */
    @Bean(name = "taskExecutor-for-pay")
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(properties.getCorePoolSize());
        executor.setMaxPoolSize(properties.getMaxPoolSize());
        executor.setQueueCapacity(properties.getQueueCapacity());
        executor.setThreadNamePrefix(properties.getThreadNamePrefix());
        // 设置线程保持活跃的时间(默认:60)
        executor.setKeepAliveSeconds(properties.getKeepAliveTime());
        // 当任务完成后,长时间无待处理任务时,销毁线程池
        executor.setWaitForTasksToCompleteOnShutdown(properties.isWaitForTasksToCompleteOnShutdown());
        executor.setAwaitTerminationSeconds(properties.getAwaitTerminationSeconds());
        // 设置任务拒绝策略
        /**
         * 4种
         * ThreadPoolExecutor类有几个内部实现类来处理这类情况:
         - AbortPolicy 丢弃任务,抛RejectedExecutionException
         - CallerRunsPolicy 由该线程调用线程运行。直接调用Runnable的run方法运行。这个很有意思,复用了请求线程。后面的请求结果中,可以看到有些线程的名字是 nio-8080-exec-1 这种格式的,证明是复用了。
         - DiscardPolicy  抛弃策略,直接丢弃这个新提交的任务
         - DiscardOldestPolicy 抛弃旧任务策略,从队列中踢出最先进入队列(最后一个执行)的任务
         * 实现RejectedExecutionHandler接口,可自定义处理器
         */
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }

    @Data
    class ThreadPoolProperties {

        /**
         * 核心线程数(默认是1):若是IO密集型,cpu核心数*2,若是cpu密集型,cpu核心数
         * 核心线程会一直存活,及时没有任务需要执行
         * 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
         * 注意:当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
         */
        private int corePoolSize = 2;
        /**
         * 最大线程数,系统默认Integer.MAX_VALUE
         */
        private int maxPoolSize = 4;
        /**
         * 允许线程空闲时间(单位:默认为秒,默认60S)
         * 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
         * 如果allowCoreThreadTimeout=true,则会直到线程数量=0
         */
        private int keepAliveTime;
        /**
         * 缓冲队列大小,系统默认Integer.MAX_VALUE
         * 注意:这个值肯定要改小,不然任务陡增时,都堆在队列中(队列值大),
         * 核心线程数就那几个,无法很快执行队列中的任务,
         * 就会延长响应时间,阻塞任务
         */
        private int queueCapacity = 3;
        /**
         * 线程池名前缀,用于监控识别
         */
        private String threadNamePrefix = "tName";

        /**
         * 允许核心线程超时(默认false)
         */
        private boolean allowCoreThreadTimeout = false;

        /**
         * 当任务完成后,长时间无待处理任务时,销毁线程池
         */
        private boolean waitForTasksToCompleteOnShutdown = false;

        private int awaitTerminationSeconds;

    }
}

跟jdk的线程池机制一样:

当往线程池中提交新任务时,线程池主要流程如下:
核心线程数 -> 线程队列 -> 最大线程数 -> 拒绝策略
a. 如果池中任务数 < corePoolSize (核心线程数),创建新线程立即执行任务
b. 如果池中任务数 > corePoolSize,新任务放到缓存队列当中等待执行
c. 队列满,线程数量<maxPoolSize,新建线程立即执行任务
d. 队列满,线程数量>=maxPoolSize,使用拒绝策略拒绝

2.3. 准备使用线程池的service

package com.example.simple.spring_scheduling;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;

/**
 * @Async失效情况: 异步方法使用static修饰
 * 2.异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类
 * 异步方法不能与被调用的异步方法在同一个类中
 * 类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
 * 如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解
 */
@Service
@EnableAsync
@Slf4j
public class ThreadPoolService {

    @Async("taskExecutor-for-pay")
    public void process(int i) throws InterruptedException {
        log.info(Thread.currentThread().getName() + "开始处理第【" + i + "】个请求");
        Thread.sleep(2 * 1000);
        log.info("第【{}】个请求处理结束", i);
    }

    public void F2() throws InterruptedException {
        for (int i = 0; i < 15; i++) {
            // 想要调起15个线程,可以吗?
            process(i);
        }
    }
}

2.4. 准备controller

package com.example.simple.controller;

import com.example.simple.jdk_concurrent.MyCallable;
import com.example.simple.spring_scheduling.ThreadPoolService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * @ClassName SampleController
 * @Description
 * @Author liukun
 * @Date 2022/6/11 17:19
 */
@RestController
@Slf4j
public class SampleController {

    @Resource
    ThreadPoolService threadPoolService;

    @Resource(name = "taskExecutor-for-pay")
    ThreadPoolTaskExecutor threadPoolTaskExecutor;

    /**
     * spring ThreadPoolTaskExecutor的使用
     * @param fun
     * @return
     * @throws InterruptedException
     */
    @RequestMapping("/spring/threads/{function}")
    public String testSpringThreadPoolTaskExecutor1(@PathVariable("function") String fun) throws InterruptedException, ExecutionException {
        if (fun.equals("f2")) {
            // 在threadPoolService.F2()方法内,循环调用10次process()方法,process()方法有异步注解,F2方法签名没有异步注解
            threadPoolService.F2();
        }
        else if (fun.equals("process")) {
            // process()方法签名有异步注解,循环调起15次。
            log.info("15个process准备调用。");
            for (int i = 0; i < 15; i++) {
                log.info("准备提交第【{}】个请求", i);
                threadPoolService.process(i);
            }
            log.info("15个process已经发到线程池了。主请求结束。");
        }
        else if (fun.equals("callable")) {
            // 向 threadPoolTaskExecutor 提交Callable任务。
            Future<String> future = threadPoolTaskExecutor.submit(new MyCallable().setName("SpringCallable"));
            // 由于执行了 future.get(), 所以请求会等到get到结果之后才能收到响应。
            log.info("线程执行的结果:" + future.get());
        }
        log.info("开始写响应给请求方。");
        return "hello world";
    }

}

2.5. 调用结果分析

http://localhost:8080/spring/threads/f2
先说结论:
public void F2() throws InterruptedException {
    for (int i = 0; i < 15; i++) {
        // 想要调起15个线程,可以吗?
        process(i);
    }
}
这段代码,看起来是想调起15个process,而process,是用异步注解的,但是实际并没有使用到线程池,打印的日志里,使用的线程都是请求线程。

调用结果:
2022-06-11 19:58:40.274  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : http-nio-8080-exec-1开始处理第【0】个请求
2022-06-11 19:58:42.276  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : 第【0】个请求处理结束
2022-06-11 19:58:42.279  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : http-nio-8080-exec-1开始处理第【1】个请求
2022-06-11 19:58:44.283  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : 第【1】个请求处理结束
2022-06-11 19:58:44.283  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : http-nio-8080-exec-1开始处理第【2】个请求
2022-06-11 19:58:46.287  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : 第【2】个请求处理结束
2022-06-11 19:58:46.287  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : http-nio-8080-exec-1开始处理第【3】个请求
2022-06-11 19:58:48.288  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : 第【3】个请求处理结束
2022-06-11 19:58:48.288  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : http-nio-8080-exec-1开始处理第【4】个请求
2022-06-11 19:58:50.293  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : 第【4】个请求处理结束
2022-06-11 19:58:50.293  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : http-nio-8080-exec-1开始处理第【5】个请求
2022-06-11 19:58:52.298  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : 第【5】个请求处理结束
2022-06-11 19:58:52.299  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : http-nio-8080-exec-1开始处理第【6】个请求
2022-06-11 19:58:54.303  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : 第【6】个请求处理结束
2022-06-11 19:58:54.304  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : http-nio-8080-exec-1开始处理第【7】个请求
2022-06-11 19:58:56.308  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : 第【7】个请求处理结束
2022-06-11 19:58:56.308  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : http-nio-8080-exec-1开始处理第【8】个请求
2022-06-11 19:58:58.309  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : 第【8】个请求处理结束
2022-06-11 19:58:58.309  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : http-nio-8080-exec-1开始处理第【9】个请求
2022-06-11 19:59:00.312  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : 第【9】个请求处理结束
2022-06-11 19:59:00.312  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : http-nio-8080-exec-1开始处理第【10】个请求
2022-06-11 19:59:02.313  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : 第【10】个请求处理结束
2022-06-11 19:59:02.314  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : http-nio-8080-exec-1开始处理第【11】个请求
2022-06-11 19:59:04.314  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : 第【11】个请求处理结束
2022-06-11 19:59:04.315  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : http-nio-8080-exec-1开始处理第【12】个请求
2022-06-11 19:59:06.318  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : 第【12】个请求处理结束
2022-06-11 19:59:06.318  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : http-nio-8080-exec-1开始处理第【13】个请求
2022-06-11 19:59:08.322  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : 第【13】个请求处理结束
2022-06-11 19:59:08.323  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : http-nio-8080-exec-1开始处理第【14】个请求
2022-06-11 19:59:10.323  INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : 第【14】个请求处理结束
2022-06-11 19:59:10.324  INFO 11561 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 开始写响应给请求方。

在继续看,在controller中循环调用15次process,能使用到线程池吗?

else if (fun.equals("process")) {
    // process()方法签名有异步注解,循环调起15次。
    log.info("15个process准备调用。");
    for (int i = 0; i < 15; i++) {
        log.info("准备提交第【{}】个请求", i);
        threadPoolService.process(i);
    }
    log.info("15个process已经发到线程池了。主请求结束。");
}

结果:是可以的,并发可以有效减少任务的执行时间。

http://localhost:8080/spring/threads/process
调用结果:
2022-06-11 20:01:51.445  INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 15个process准备调用。
2022-06-11 20:01:51.445  INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 准备提交第【0】个请求
2022-06-11 20:01:51.449  INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 准备提交第【1】个请求
2022-06-11 20:01:51.450  INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 准备提交第【2】个请求
2022-06-11 20:01:51.450  INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 准备提交第【3】个请求
2022-06-11 20:01:51.450  INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 准备提交第【4】个请求
2022-06-11 20:01:51.450  INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 准备提交第【5】个请求
2022-06-11 20:01:51.450  INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 准备提交第【6】个请求
2022-06-11 20:01:51.451  INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 准备提交第【7】个请求
2022-06-11 20:01:51.455  INFO 11619 --- [         tName1] c.e.s.s.ThreadPoolService                : tName1开始处理第【0】个请求
2022-06-11 20:01:51.455  INFO 11619 --- [         tName4] c.e.s.s.ThreadPoolService                : tName4开始处理第【6】个请求
2022-06-11 20:01:51.455  INFO 11619 --- [         tName2] c.e.s.s.ThreadPoolService                : tName2开始处理第【1】个请求
2022-06-11 20:01:51.455  INFO 11619 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : http-nio-8080-exec-1开始处理第【7】个请求
2022-06-11 20:01:51.455  INFO 11619 --- [         tName3] c.e.s.s.ThreadPoolService                : tName3开始处理第【5】个请求
2022-06-11 20:01:53.459  INFO 11619 --- [         tName1] c.e.s.s.ThreadPoolService                : 第【0】个请求处理结束
2022-06-11 20:01:53.459  INFO 11619 --- [         tName2] c.e.s.s.ThreadPoolService                : 第【1】个请求处理结束
2022-06-11 20:01:53.460  INFO 11619 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : 第【7】个请求处理结束
2022-06-11 20:01:53.459  INFO 11619 --- [         tName4] c.e.s.s.ThreadPoolService                : 第【6】个请求处理结束
2022-06-11 20:01:53.460  INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 准备提交第【8】个请求
2022-06-11 20:01:53.460  INFO 11619 --- [         tName2] c.e.s.s.ThreadPoolService                : tName2开始处理第【3】个请求
2022-06-11 20:01:53.460  INFO 11619 --- [         tName1] c.e.s.s.ThreadPoolService                : tName1开始处理第【2】个请求
2022-06-11 20:01:53.460  INFO 11619 --- [         tName3] c.e.s.s.ThreadPoolService                : 第【5】个请求处理结束
2022-06-11 20:01:53.460  INFO 11619 --- [         tName4] c.e.s.s.ThreadPoolService                : tName4开始处理第【4】个请求
2022-06-11 20:01:53.460  INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 准备提交第【9】个请求
2022-06-11 20:01:53.461  INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 准备提交第【10】个请求
2022-06-11 20:01:53.462  INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 准备提交第【11】个请求
2022-06-11 20:01:53.462  INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 准备提交第【12】个请求
2022-06-11 20:01:53.462  INFO 11619 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : http-nio-8080-exec-1开始处理第【12】个请求
2022-06-11 20:01:53.462  INFO 11619 --- [         tName5] c.e.s.s.ThreadPoolService                : tName5开始处理第【11】个请求
2022-06-11 20:01:55.462  INFO 11619 --- [         tName1] c.e.s.s.ThreadPoolService                : 第【2】个请求处理结束
2022-06-11 20:01:55.462  INFO 11619 --- [         tName4] c.e.s.s.ThreadPoolService                : 第【4】个请求处理结束
2022-06-11 20:01:55.462  INFO 11619 --- [         tName2] c.e.s.s.ThreadPoolService                : 第【3】个请求处理结束
2022-06-11 20:01:55.463  INFO 11619 --- [         tName5] c.e.s.s.ThreadPoolService                : 第【11】个请求处理结束
2022-06-11 20:01:55.463  INFO 11619 --- [         tName2] c.e.s.s.ThreadPoolService                : tName2开始处理第【10】个请求
2022-06-11 20:01:55.463  INFO 11619 --- [         tName4] c.e.s.s.ThreadPoolService                : tName4开始处理第【9】个请求
2022-06-11 20:01:55.463  INFO 11619 --- [         tName1] c.e.s.s.ThreadPoolService                : tName1开始处理第【8】个请求
2022-06-11 20:01:55.463  INFO 11619 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService                : 第【12】个请求处理结束
2022-06-11 20:01:55.464  INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 准备提交第【13】个请求
2022-06-11 20:01:55.464  INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 准备提交第【14】个请求
2022-06-11 20:01:55.464  INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 15个process已经发到线程池了。主请求结束。
2022-06-11 20:01:55.464  INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController   : 开始写响应给请求方。
2022-06-11 20:01:57.464  INFO 11619 --- [         tName4] c.e.s.s.ThreadPoolService                : 第【9】个请求处理结束
2022-06-11 20:01:57.464  INFO 11619 --- [         tName2] c.e.s.s.ThreadPoolService                : 第【10】个请求处理结束
2022-06-11 20:01:57.464  INFO 11619 --- [         tName1] c.e.s.s.ThreadPoolService                : 第【8】个请求处理结束
2022-06-11 20:01:57.464  INFO 11619 --- [         tName4] c.e.s.s.ThreadPoolService                : tName4开始处理第【13】个请求
2022-06-11 20:01:57.464  INFO 11619 --- [         tName1] c.e.s.s.ThreadPoolService                : tName1开始处理第【14】个请求
2022-06-11 20:01:59.465  INFO 11619 --- [         tName4] c.e.s.s.ThreadPoolService                : 第【13】个请求处理结束
2022-06-11 20:01:59.465  INFO 11619 --- [         tName1] c.e.s.s.ThreadPoolService                : 第【14】个请求处理结束

最后就是简单了解一下,是否可以向threadPoolTaskExecutor提交callable任务?就是说在线程执行完成后获取到结果?

else if (fun.equals("callable")) {
    // 向 threadPoolTaskExecutor 提交Callable任务。
    Future<String> future = threadPoolTaskExecutor.submit(new MyCallable().setName("SpringCallable"));
    // 由于执行了 future.get(), 所以请求会等到get到结果之后才能收到响应。
    log.info("线程执行的结果:" + future.get());
}

上面threadPoolTaskExecutor是通过名称注入到controller中的:
@Resource(name = "taskExecutor-for-pay")
ThreadPoolTaskExecutor threadPoolTaskExecutor;

执行结果也简单,就是在拿到结果之后,写响应给调用方。
当然,如果我们没有显示的调用get()方法,那就会直接写响应了。
2022-06-12 23:21:37.152  INFO 19311 --- [nio-8080-exec-4] c.e.simple.controller.SampleController   : 线程执行的结果:Callable[SpringCallable]睡了:1秒
2022-06-12 23:21:37.155  INFO 19311 --- [nio-8080-exec-4] c.e.simple.controller.SampleController   : 开始写响应给请求方。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
将SonarQube 8.9.3迁移到Kubernetes 1.24.1需要考虑以下几个步骤和注意事项: 1. 准备Kubernetes集群:确保您已经准备好一个运行Kubernetes 1.24.1版本的集群。可以使用Kubernetes发行版或云提供商的托管服务。 2. 创建SonarQube镜像:根据SonarQube 8.9.3版本的要求,构建一个适用于Kubernetes的Docker镜像。您可以使用Dockerfile或Docker Compose文件创建镜像,并将其推送到您的容器注册表中。 3. 配置持久化存储:在Kubernetes上运行SonarQube时,建议使用持久化存储来保存SonarQube的数据和配置文件。您可以选择使用Kubernetes提供的持久卷或云提供商的存储解决方案。 4. 创建Kubernetes部署和服务:根据SonarQube的要求,创建一个Kubernetes部署和服务来运行SonarQube容器。您可以使用Deployment对象来定义Pod模板,并使用Service对象将SonarQube暴露给外部访问。 5. 迁移数据库:如果您使用了外部数据库(如MySQL或PostgreSQL),需要确保在迁移过程中将现有的数据库迁移到新的Kubernetes环境中。可以使用数据库备份、导出和导入等方法来完成这个步骤。 存在的风险和注意事项: 1. 版本兼容性:SonarQube 8.9.3和Kubernetes 1.24.1之间可能存在版本兼容性问题。在进行迁移之前,建议查阅SonarQube和Kubernetes的官方文档,确保所选版本之间的兼容性。 2. 数据丢失:在迁移过程中,存在数据丢失的风险。确保在迁移之前进行数据备份,并验证迁移后的数据完整性和可用性。 3. 资源调整:SonarQube在Kubernetes上的资源需求可能与传统部署方式有所不同。确保为SonarQube容器分配足够的资源(CPU、内存等),以避免性能问题和崩溃。 4. 配置和插件:迁移后,需要重新配置SonarQube并重新安装任何使用的插件。确保备份和记录现有的配置和插件列表,并在迁移后进行适当的恢复。 5. 监控和日志:在迁移后,确保正确设置监控和日志收集,以便及时发现和解决任何问题。 在进行任何生产环境中的迁移操作之前,建议先在测试环境中进行测试和验证,以减少潜在的风险,并确保成功完成迁移过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值