每隔一个小时算帖子的分数,每隔半小时清理服务器的临时文件,这些需求都需要用到任务调度组件来解决。
任务调度组件是基于多线程的,但凡用多线程,一定会用到线程池,因为创建线程是有开销的,且开销较大。使用线程池来管理线程,能够让线程复用,提高处理能力,节约资源。
线程池也是重点。
jdk线程池是jdk自带的,spring线程池是spring自带的, 其中executor services和thread pool task executor是普通线程池
scheduled executor services以及 thread pool task scheduler所创建的线程可以执行定时任务。但是分布式的时候,这俩是有问题的,scheduler不合适,因为scheduler程序运行所依赖的参数都是存在数据库中的,scheduler没有解决分布式的问题。
比如,每隔十分钟,删掉一个临时文件,两个都会这样做,会产生一定冲突
jdk和spring的定时任务组件,都是基于内存的,配置多长时间运行一次,配置参数都在内存中,但是服务器1和2内存不共享,不能知道各自正在干嘛,因此会产生一定冲突
因此分布式下用Quartz更多
Quartz程序运行所依赖的参数都是存在数据库(DB)里面的,因此不管部署多少个Quartz,都会访问同一个数据库。(服务器可以有多个,但是数据库只有一个)
浏览器把请求发给Nginx,Nginx依据一定策略,选择服务器对请求进行处理,如果是普通请求,则是分配给controller处理
如果改为Quartz,可以对不同的请求进行排队操作。
我们最终的目的是,使用Quartz,但是在这之前,应该先学会用 jdk和spring线程池。
接下来创建test
ThreadPoolTests
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class ThreadPoolTests {
private static final Logger logger = LoggerFactory.getLogger(ThreadPoolTests.class);//logger在输出内容的时候,自然会带上线程的id以及时间
// JDK普通线程池
private ExecutorService executorService = Executors.newFixedThreadPool(5);//通过工厂Executors来实例化,实例化后包含五个线程,反复复用这五个线程
// JDK可执行定时任务的线程池
private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
// Spring普通线程池
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
// Spring可执行定时任务的线程池
@Autowired
private ThreadPoolTaskScheduler taskScheduler;
@Autowired
private AlphaService alphaService;
//ThreadPoolTests是junit测试方法,其和main方法不一样,如果在main方法里启动线程,该线程不挂掉的话,main会挡着ThreadPoolTests执行,使得main会挡着ThreadPoolTests执行,不会立刻结束。
//但是junit测试方法 每启动一个子线程,和当前线程是并发的,如果test方法没有逻辑,立刻就结束了,不管启动的线程有没有完成。
//因此当test方法启动完一个线程以后,等其执行完以后在关闭线程。则让当前主线程sleep(阻塞)一会儿
private void sleep(long m) {//m单位是毫秒
try {
Thread.sleep(m);//当前线程阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 1.JDK普通线程池
@Test
public void testExecutorService() {//需要给线程池一个任务 来使得线程池 分配一个线程去执行。任务即是线程体。
Runnable task = new Runnable() {
@Override
public void run() {
logger.debug("Hello ExecutorService");
}
};
for (int i = 0; i < 10; i++) {//执行10次
executorService.submit(task);//每调用一个submit方法,线程池都会给其分配一个线程以执行线程体。
}
sleep(10000);//不然,线程还没执行完,方法就结束了。1w毫秒就是10s
}
结果:
1-5个线程来回使用
输出内容:
// 2.JDK定时任务线程池(设置时间间隔不断执行,要提供一个线程体)
@Test
public void testScheduledExecutorService() {
Runnable task = new Runnable() {
@Override
public void run() {
logger.debug("Hello ScheduledExecutorService");
}
};
scheduledExecutorService.scheduleAtFixedRate(task, 10000, 1000, TimeUnit.MILLISECONDS);//第一个参数是任务。第二个参数是该任务延迟多少ms才执行。第三个参数是时间间隔ms,第三个参数是声明时间单位TimeUnit.MILLISECONDS
sleep(30000);
}
在application properties中增加
# TaskExecutionProperties spring普通线程池的配置
# 线程池中有几个核心线程
spring.task.execution.pool.core-size=5
# 当核心线程不够用,最多扩展到15个
spring.task.execution.pool.max-size=15
#queue-capacity指队列容量,当15个线程还是不够用,需要将其放在队列中等候。设置队列能缓冲一百个任务
spring.task.execution.pool.queue-capacity=100
# TaskSchedulingProperties spring能启动定时任务的线程池的配置
spring.task.scheduling.pool.size=5
// Spring普通线程池
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
// 3.Spring普通线程池
@Test
public void testThreadPoolTaskExecutor() {
Runnable task = new Runnable() {//声明线程体
@Override
public void run() {
logger.debug("Hello ThreadPoolTaskExecutor");
}
};
for (int i = 0; i < 10; i++) {
taskExecutor.submit(task);
}
sleep(10000);
}
执行的时候会报错
在ThreadPoolConfig中
@Configuration
@EnableScheduling
//如果不加EnableScheduling,则表明定时任务没有开启
@EnableAsync//使AlphaService中的@Async注解生效
public class ThreadPoolConfig {
}
执行结果:
Spring普通线程池 比jdk线程池更灵活,因为可以设置 核心线程个数,扩展线程个数
// Spring可执行定时任务的线程池
@Autowired
private ThreadPoolTaskScheduler taskScheduler;
// 4.Spring定时任务线程池
@Test
public void testThreadPoolTaskScheduler() {
Runnable task = new Runnable() {
@Override
public void run() {
logger.debug("Hello ThreadPoolTaskScheduler");
}
};
Date startTime = new Date(System.currentTimeMillis() + 10000);//当前时间在延迟一万毫秒,就是现在的时间
taskScheduler.scheduleAtFixedRate(task, startTime, 1000);//以固定频率执行,执行时间间隔
sleep(30000);
}
// 5.Spring普通线程池(简化)
@Test
public void testThreadPoolTaskExecutorSimple() {
for (int i = 0; i < 10; i++) {
alphaService.execute1();
}
sleep(10000);//不然线程还没执行,方法已经结束
}
在AlphaService中,
// 让该方法在多线程环境下,被异步的调用.即启动一个线程来执行此方法,该方法和主线程是并发(异步)执行的
@Async//之前ThreadPoolConfig中 @EnableAsync就是让此配置生效。
public void execute1() {
logger.debug("execute1");
}
线程为啥要sleep呢
// 6.Spring定时任务线程池(简化)
@Test
public void testThreadPoolTaskSchedulerSimple() {
sleep(30000);
}
接下来演示Quartz,但是Quartz是依赖于数据库的。
DB有一套表,需要我们提前创建。这个表就是Quartz 所需要的表
该表即为Quartz依赖的表
我们需要用命令行工具,用source命令,把其导入community这个库中。
导入后:
47.12
在maven里导入Quartz的包
然后看看源码:
job、Scheduler、jobdetail(用来配置job)、trigger触发器(设置以什么样的频率反复运行)
通过job接口定义一个任务,通过jobdetail以及trigger接口来配置job,主要做这三个事情。
配置好,重新启动,Quartz就会重新启动配置信息,并将读到的配置信息存到数据库(表)中,Quartz以后都会读取表中这些信息,来执行任务。
trigger第一次启动服务时用,以后就不用了
通过jobdetail配置的信息,都存到这个表里。
任务的名字
job的名字
job的分组
job的描述
job对应的类
…
新建AlphaJob 实现Job 接口
public class AlphaJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println(Thread.currentThread().getName() + ": execute a quartz job.");//打印当前线程的名字+: execute a quartz job.
}
}
QuartzConfig
// 配置(仅在第一次被读取到) -> 信息被初始化到数据库 -> quartz访问数据库去调用该任务,不在访问配置文件
@Configuration
public class QuartzConfig {
//FactoryBean和之前一开始学的IOC 学到的 BeanFactory有本质区别;BeanFactory是整个IOC容器的顶层接口
// FactoryBean主要目的是 简化Bean的实例化过程,因为有的Bean实例化过程比较复杂:
// 1.通过FactoryBean封装Bean的实例化过程.
// 2.将FactoryBean装配到Spring容器里.
// 3.将FactoryBean注入给其他的Bean.
// 4.该Bean得到的是FactoryBean所管理的对象实例.
// 配置JobDetail
// @Bean
public JobDetailFactoryBean alphaJobDetail() {//Bean的名字是alphaJobDetail。初始化该bean,想当于将其装配到容器中
JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();//实例化对象
factoryBean.setJobClass(AlphaJob.class);
factoryBean.setName("alphaJob");//声明job任务的名字
factoryBean.setGroup("alphaJobGroup");//声明任务的组
factoryBean.setDurability(true);//声明任务是否长久保存,哪怕任务不再运行。连触发器都没有了,也会一直报存
factoryBean.setRequestsRecovery(true);//声明任务是否可恢复
return factoryBean;
}
// 配置Trigger(SimpleTriggerFactoryBean比较简单,每十天要做...; CronTriggerFactoryBean复杂,每个月月底前两天要做...)
// @Bean
public SimpleTriggerFactoryBean alphaTrigger(JobDetail alphaJobDetail) {//Trigger依赖于JobDetail,因此需要读取
SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
factoryBean.setJobDetail(alphaJobDetail);
factoryBean.setName("alphaTrigger");
factoryBean.setGroup("alphaTriggerGroup");
factoryBean.setRepeatInterval(3000);//多长时间执行一次任务
factoryBean.setJobDataMap(new JobDataMap());//Trigger底层需要存储一些状态,新建JobDataMap对象来存储
return factoryBean;
}
测试:
Quartz底层也依赖于线程池,线程池有一默认配置,如果想重新配置底层线程池,需要在application properties中进行配置。
```c
# QuartzProperties
spring.quartz.job-store-type=jdbc//任务用jdbc来存储
spring.quartz.scheduler-name=communityScheduler//调度器的名字
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO//调度器的id 自动生成
spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate//驱动StdJDBCDelegate
spring.quartz.properties.org.quartz.jobStore.isClustered=true//是否采用集群方式,是
spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool//用org.quartz.simpl.SimpleThreadPool线程池
spring.quartz.properties.org.quartz.threadPool.threadCount=5//线程数量
做了如上配置以后,
表里出现了如下信息:
新建QuartzTests
package com.nowcoder.community;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class QuartzTests {
@Autowired
private Scheduler scheduler;
@Test
public void testDeleteJob() {
try {
boolean result = scheduler.deleteJob(new JobKey("alphaJob", "alphaJobGroup"));
System.out.println(result);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
返回true
确实删除了
这里面存着不是job,而是scheduler,所以东西还在