赞
踩
刚刚接触到这个领域的时候,我感觉无从下手。一边查资料一边写代码看运行日志,碰到了一些坑,也学到了很多知识—不仅仅是并发编程方面的,也包括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的方法很多,我这里不多说。我们采取的是其中一种,即实现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; } }
启动类main方法里获取bean的方法是:
MonitorTask m1= ApplicationContextProvider.getBean("mTask",MonitorTask.class);
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()); //相关业务代码 } }
启动类里的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());
}
结果如下,可以看到是除了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容器管理
}
}
测试类代码如下:
@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()); } }
这里,使用了两个注解,@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());
}
输出结果如下:
自定义线程池代码里配置了3个参数,corePoolSize、queueCapacity、maxPoolSize。corePoolSize表示线程池核心线程数,即正常情况下开启的线程数量。queueCapacity是当核心线程都在跑任务的时候,还有多余的任务会存到这个队列。如果这个队列没满的话,会一直等到核心线程跑完任务,再一个接一个地进入核心线程去执行任务,也就是一直都最多只有corePoolSize个核心线程在跑。如果queueCapacity满了,还有任务,那就会启动更多的线程,可以额外启动的线程数量取决于maxPoolSize,也就是说,最多可以有maxPoolSize个线程在运行,排队的最多有queueCapacity个,即最多有queueCapacity+maxPoolSize个任务,如果任务数超过了,会根据拒绝策略进行处理。拒绝策略有多种:由任务调用线程执行、抛异常、多余的直接抛弃、根据FIFO(先进先出)抛弃队列里任务。当然了,maxPoolSize不能比corePoolSize小,否则也会报错。在线程池配置中可以配置多个线程池,如果没有在@Async后指定value,那么会自动寻找配置的所有线程。
还有很多东西要写。。。。待我深入研究一下
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。