当前位置:   article > 正文

springboot里多线程运行某任务_spring并发多个线程执行一个方法

spring并发多个线程执行一个方法

前言

刚刚接触到这个领域的时候,我感觉无从下手。一边查资料一边写代码看运行日志,碰到了一些坑,也学到了很多知识—不仅仅是并发编程方面的,也包括spring、IoC和bean的知识。在这里做个记录吧!

正文

需求

一般情况下,在Spring的项目中很少有使用多线程处理任务的,没错,大多数时候我们都是使用Spring MVC开发的web项目,默认的Controller,Service,Dao组件的作用域都是单实例,无状态,然后被并发多线程调用。现在,我们的项目是一个服务平台项目,对外提供大量的服务api让用户调用。在我们的项目里还用到了服务路由(准备再写一篇博客讲这个),什么是服务路由呢,就是我们真正供用户调用的服务api实际上都是第三方提供的服务api,只不过用的url是我们的url。现在,我们的需求是:在项目运行的时候定期检测这些第三方服务是否可用,并提供服务质量的报告,如果第三方服务不可用,在我们服务平台上将对应的api停掉。很自然地想到,这样一个监测功能应该在后台运行,不能和项目原先的功能冲突。因此,我们要在启动类的main方法里开辟新的线程去执行监控的这个功能。
考虑另外一个事情,如何在启动类里面去执行某个controller的某个方法呢?一般来说,执行controller内的方法都是在外部访问这个方法的url。那么,在启动类的main方法里如何访问这个controller呢?说起来其实这是个挺简单的事情:把controller当做一个普通的类即可,如果是访问某个static方法,问题变得很简单,直接在main方法里调用这个controller类名.方法即可。如果不是static方法(通常都不是static方法),则需要controller的实例,也就是这个bean,直接new出来的实例是没法使用的,即使可以new成功,bean里面依赖的其他组件比如dao,是没法初始化的。这是因为绕过了spring,默认的spring初始化一个类的时候,其相关依赖的组件都会被初始化,而自己new出来的实例,不具备这种功能。你或许会想,可以使用依赖注入的方法,利用注解@Autowired将controller实例注入,实际上这是不可行的,因为main方法是个static方法,不可以使用非static变量,而如果使用static修饰controller变量,那么是不能注入的,这是因为spring容器管理的都是对象的属性,而static变量是属于类的,具体可见其他博客。
那到底该怎么办?我们还是要在启动类的main方法里获取controller的bean,然后去执行controller实例里面的方法。项目启动之后,项目里配置的所有bean都被spring管理,我们需要手动地获取ApplicationContext,然后再利用getBean方法去手动地获得bean。

手动获取bean

手动获取bean的方法很多,我这里不多说。我们采取的是其中一种,即实现ApplicationContextAware接口,让这个实现类作为一个bean被spring管理,getBean为static方法,可以直接在main方法里调用,即可通过bean的名称获取bean,代码如下:

@Component
public class ApplicationContextProvider implements ApplicationContextAware {
    private static ApplicationContext context;

    private ApplicationContextProvider(){}

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)throws BeansException{
        context=applicationContext;
    }

    public static <T> T getBean(String name,Class<T> aClass){
        return context.getBean(name,aClass);
    }

    public static ApplicationContext getContext(){
        return context;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

启动类main方法里获取bean的方法是:

MonitorTask m1= ApplicationContextProvider.getBean("mTask",MonitorTask.class);
  • 1

MonitorTask类同样要被spring管理,bean的名称自定义为“mTask”。
这里的原理还有待进一步挖掘。

另开线程运行某任务

有两种方法可以完成这个功能。第一,定义自己的线程类,即extends Thread类,重写run方法,在run方法里写业务逻辑。然后注册为bean,让spring去获取这个线程类,在main方法里获取这个bean,调用start方法启动即可。第二,是自定义线程池,用@Async和@EnableAsync注解去做这件事。

自定义线程类

首先是线程类

@Component("mTask")
@Scope("prototype")
public class MonitorTask extends Thread {
    final static Logger logger= LoggerFactory.getLogger(MonitorTask.class);
    private Monitor monitor;
    public void setMonitor(Monitor monitor){
        this.monitor=monitor;
    }
    @Autowired
    private IApiService iApiService;

    @Override
    public void run(){
        System.out.println("MonitorTask里线程id是"+ Thread.currentThread().getId());
        System.out.println("MonitorTask里线程name是"+Thread.currentThread().getName());
        //相关业务代码
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

启动类里的main方法:

public static void main(String[] args){
		SpringApplication.run(ServiceGridApplication.class, args);
		MonitorTask m1=ApplicationContextProvider.getBean("mTask",MonitorTask.class);
		m1.start();
		System.out.println("main函数线程id是"+Thread.currentThread().getId());
		System.out.println("main函数线程name是"+Thread.currentThread().getName());
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

结果如下,可以看到是除了main线程之外还有个线程。
在这里插入图片描述
这里有一点需要注意的是,线程类必须要调用start方法才能启动一个新的线程,start方法里调用了run方法,因此业务代码也会执行的。如果直接调用run方法,就和一个普通类没什么区别了(线程学的太差了这个地方我都犯了错。。),验证结果如下:
在这里插入图片描述
因为业务代码运行很久,单线程,所以最下面两行还没打印出来,从MonitorTask里打印的结果就知道是主线程。

自定义线程池

自定义线程类肉眼可见的问题是,一个线程类只能有一个run方法,如果想多开几个线程,需要写多个线程类,比较麻烦。可以自定义线程池,设置线程池corePoolSize、queueCapacity、maxPoolSize等参数,即可控制开辟的线程数量。定义线程池代码如下:

@Configuration
@EnableAsync
public class ThreadPoolConfig{
    @Bean("threadPoolA")
    public Executor getExecutorA(){
        ThreadPoolTaskExecutor executor=new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(25);
        executor.initialize();
        return executor;//返回ThreadPoolTaskExecutor被spring容器管理
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

测试类代码如下:

@Component
public class SynTest {

    @Autowired
    private IApiService iApiService;
    @Async(value = "threadPoolA")
    public void  monitor() throws InterruptedException {
        System.out.println("monitor的线程id是"+Thread.currentThread().getId());
        System.out.println("monitor的线程name是"+Thread.currentThread().getName());
    }

    @Async(value = "threadPoolA")
    public void alert() throws InterruptedException {
        System.out.println("alert的线程id是"+Thread.currentThread().getId());
        System.out.println("alert的线程name是"+Thread.currentThread().getName());
    }

    @Async(value = "threadPoolA")
    public void apimonitor(){
        System.out.println("apimonitor的线程id是"+Thread.currentThread().getId());
        System.out.println("apimonitor的线程name是"+Thread.currentThread().getName());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

这里,使用了两个注解,@EnableAsync和@Async,显而易见,@EnableAsync配置可以异步执行方法,在需要异步执行的方法前加上@Async注解,如果线程池配置的poolSize够大的话,即可为每个方法分配一个线程异步执行。启动类main方法代码如下:

public static void main(String[] args) throws InterruptedException {
		SpringApplication.run(ServiceGridApplication.class, args);
		SynTest synTest=ApplicationContextProvider.getBean("synTest",SynTest.class);
		synTest.monitor();
		synTest.alert();
		synTest.apimonitor();
		System.out.println("main函数线程id是"+Thread.currentThread().getId());
		System.out.println("main函数线程name是"+Thread.currentThread().getName());

	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

输出结果如下:
在这里插入图片描述

自定义线程属性解析

自定义线程池代码里配置了3个参数,corePoolSize、queueCapacity、maxPoolSize。corePoolSize表示线程池核心线程数,即正常情况下开启的线程数量。queueCapacity是当核心线程都在跑任务的时候,还有多余的任务会存到这个队列。如果这个队列没满的话,会一直等到核心线程跑完任务,再一个接一个地进入核心线程去执行任务,也就是一直都最多只有corePoolSize个核心线程在跑。如果queueCapacity满了,还有任务,那就会启动更多的线程,可以额外启动的线程数量取决于maxPoolSize,也就是说,最多可以有maxPoolSize个线程在运行,排队的最多有queueCapacity个,即最多有queueCapacity+maxPoolSize个任务,如果任务数超过了,会根据拒绝策略进行处理。拒绝策略有多种:由任务调用线程执行、抛异常、多余的直接抛弃、根据FIFO(先进先出)抛弃队列里任务。当然了,maxPoolSize不能比corePoolSize小,否则也会报错。在线程池配置中可以配置多个线程池,如果没有在@Async后指定value,那么会自动寻找配置的所有线程。

后记

还有很多东西要写。。。。待我深入研究一下

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/197701
推荐阅读
相关标签
  

闽ICP备14008679号