Spring Boot 到底是单线程还是多线程

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

前言

我们知道用Spring Boot来构建项目非常的方便,开发过程中我们会使用它来快速的构建项目,自带tomcat容器、启动也会将需要配置的bean全部加载到我们的Spring容器里面。但是在调用Controller方法的时候,是单线程、还是多线程的呢,今天我们来一起研究下。

执行Controller

首先我们通过执行两个Controller方法,发现打印的日志,执行这两个方法分别是不同的线程,但是对创建的变量进行了叠加,因为Spring的默认是以单例模式创建的,所以就会导致线程不安全。

4ABF2C5E-970D-45EB-8570-0FF05C213A30.png

我们在Çontroller头部加入@Scope,使它的作用域变成原型模式,这时候我们再次执行方法。

@RequestMapping(“test”)
@Scope(“prototype”)
@RestController
public class TestController {
复制代码

可以看到依旧是两个线程执行,但是我们变量的操作没有进行类加,而是分别进行了i++的操作,证明每次的reqeust请求都会创建一个bean。 注意⚠️:我们写代码的时候千万不能在Çontroller里面定义常量,然后方法里面进行操作,会有很大的问题,这里只是做演示。

313B2929-F70A-42D2-B795-EB0E8FF40F67.png

由此可见Spring Boot中Controller默认是单例模式,并且执行的时候是多线程的方式。

@Async

既然提到了这里,我们就来说下Spring Boot的一个多线程首先原理,先来看下我们的异步执行注解@Async,通过使用注解的方式,在我们需要异步执行方法的场景下,实现一个异步执行。 当然也离不开@EnableAsync这个注解,它是一个异步注解的开关。可以看到使用的是代理的模式。

6F468EC9-F4F5-4D01-B0B8-7FA40CA5DBD6.png

通过看源码追踪到AsyncTaskExecutor 这个接口,看名字就知道是异步任务的一个线程池创建的接口,继承的TaskExecutor父接口。 35F16DD2-487D-48E8-8616-2506F0C4D7E6.png

可以看到ThreadPoolTaskExecutor这个线程池的配置类,可以看到异步操作其实是由创建线程池来完成的,通过向线程池提交task任务,用Future类接收,可以获取异步任务的执行结果。

72C52829-4D35-4836-8077-E40D81848700.png

F82C6660-8090-497C-981F-59AB0278BC44.png

使用Async

下面来通过自定线程池,然后先线程池提交我们的异步任务。

AsyncService异步方法

@Service
public class AsyncService {

    private Logger logger = LoggerFactory./getLogger/(TestController.class);

    @Async(“taskExecutor”)
    public String getAsyncMessage(String message) {
        try {
            logger.info(“执行异步方法,message is “ + message);
            Thread./sleep/(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        return message;
    }
复制代码

AsyncConfig异步线程池配置

public class AsyncConfig {

    @Bean(“taskExecutor”)
    public Executor taskExecutor(){

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数:线程池创建时候初始化的线程数
        executor.setCorePoolSize(10);
        // 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        executor.setMaxPoolSize(20);
        // 缓冲队列:用来缓冲执行任务的队列
        executor.setQueueCapacity(500);
        // 允许线程的空闲时间60秒:当超过了核心线程之外的线程在空闲时间到达之后会被销毁
        executor.setKeepAliveSeconds(60);
        // 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix(“Async-task-“);
        // 缓冲队列满了之后的拒绝策略:由调用线程处理(一般是主线程)
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;

    }
复制代码

执行结果,我们可以看见异步方法通过我们定义的线程池提交,没有按照for循环的顺序提交,而是实现了异步的提交。

E6DE8970-61B8-41C0-979A-E089423A0793.png

总结

Spring Boot框架是一个可以实现多线程的框架,不是一个线程安全的框架,我们要使它变成线程安全,还需要集成其他组件,包括用到一些线程安全的类,但是可以说明Spring Boot在执行Controller方法的时候确实是以多线程的方式执行的,包括异步的方法。

猜你喜欢

转载自juejin.im/post/7128312708599906318