Executor多线程与定时任务

点击上方“罗晓胜”,马上关注,您的支持对我帮助很大

 

上期文章

 

 

 

/   前言   /

 

老在听人说多线程,那么啥是多线程,定时任务又是怎么一会儿事,本文会简单介绍多线程的创建和使用,以及定时任务的到底是怎么在使用和工作,更加详细的介绍会在java并发连载文章中介绍。

 

/   正文   /

 

创建定时任务的方式很多,常见的如Spring定时任务,Executors定时任务,还有其他定时任务。

 

Spring定时任务

1.开始定时任务能力 只需要在配置类中添加一个@EnableScheduling注解即可开启SpringTask的定时任务能力。

@Configuration
@EnableScheduling
public class SpringTaskConfig {
}

2.定义定时任务类

@Component
public class ScheduledTask {

	//定时任务方法
	@Scheduled(fixedDelay = 5000)        //fixedDelay = 5000表示当前方法执行完毕5000ms后,Spring scheduling会再次调用该方法
	public void getTask1() {
	    System.out.println("当前时间:" + new Date());
	  }
}

注解说明:

  • @Scheduled(fixedRate=2000):上一次开始执行时间点后2秒再次执行;

  • @Scheduled(fixedDelay=2000):上一次执行完毕时间点后2秒再次执行;

  • @Scheduled(initialDelay=1000, fixedDelay=2000):第一次延迟1秒执行,然后在上一次执行完毕时间点后2秒再次执行;

  • @Scheduled(cron="* * * * * ?"):按cron规则执行

  • cron表达式:Seconds Minutes Hours DayofMonth Month DayofWeek [Year]

  • 例:

    • 每10分钟扫描一次 @Scheduled(cron = "0 0/10 * ? * ?") (cron = "0 0 0 * * ?") 每天凌晨执行一次


Executor多线程与定时任务

从JDK1.5开始,Java API提供了Executor框架让你可以创建不同的线程池

Executors类说明:

JDK1.5中提供Executors工厂类来产生连接池,该工厂类中包含如下的几个静态工程方法来创建连接池: 

1、public static ExecutorService newFixedThreadPool(int nThreads):创建一个可重用的、具有固定线程数的线程池。 

2、public static ExecutorService newSingleThreadExecutor():创建一个只有单线程的线程池,它相当于newFixedThreadPool方法是传入的参数为1 

3、public static ExecutorService newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中。

4、public static ScheduledExecutorService newSingleThreadScheduledExecutor:创建只有一条线程的线程池,他可以在指定延迟后执行线程任务 

5、public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务,corePoolSize指池中所保存的线程数,即使线程是空闲的也被保存在线程池内。

说明:4和5加了的Scheduled,也就是具备了定时任务的能力

Executors定时方法说明:

Executors中的方法 schedule(Callable callable, long delay, TimeUnit unit) 创建并执行在给定延迟后启用的 ScheduledFuture。

schedule(Runnable command, long delay, TimeUnit unit) 创建并执行在给定延迟后启用的一次性操作。

scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnitunit) 创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;也就是将在 initialDelay 后开始执行,然后在initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推。

scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit) 创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。

配置多线程原理

ScheduledTaskRegistrar中,判断taskScheduler为空,那么就给定时任务做了一个单线程的线程池

if (this.taskScheduler == null) {
	this.localExecutor = Executors.newSingleThreadScheduledExecutor();
	this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}

ScheduledTaskRegistrar此类中提供设置taskScheduler方法:

public void setScheduler(Object scheduler) {
	Assert.notNull(scheduler, "Scheduler object must not be null");
	if (scheduler instanceof TaskScheduler) {
		this.taskScheduler = (TaskScheduler) scheduler;
	}
	else if (scheduler instanceof ScheduledExecutorService) {
		this.taskScheduler = new ConcurrentTaskScheduler(((ScheduledExecutorService) scheduler));
	}
	else {
		throw new IllegalArgumentException("Unsupported scheduler type: " + scheduler.getClass());
	}
}

所以,只需要显式的设置一个ScheduledExecutorService就可以达到并发的效果了

Configuration配置方式

@Configuration //所有的定时任务都放在一个线程池中,定时任务启动时使用不同的线程。

public class ScheduleConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { //设定一个长度10的定时任务线程池 taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10)); } }

手动配置

  1. 手动配置ScheduledExecutorService ,手动执行任务

        Executors.newSingleThreadExecutor();    // 创建单线程线程池
        Executors.newCachedThreadPool();    // 创建具有缓存功能的线程池
        Executors.newFixedThreadPool(5);    // 创建一个可重用的、具有固定线程数的线程池
        
        Executors.newSingleThreadScheduledExecutor  // 创建单线程定时任务线程
        Executors.newScheduledThreadPool(3);    // 创建指定线程数定时任务线程池
  1. 执行多线程定时任务

/**
* 参数说明
* Callable/runable:the function/task to execute
* task(TimerTask):要执行的任务
* delay(Long):多久后去执行
* period(Long):每隔多久执行一次
* unit:计时单位
*/
//执行单次定时任务
executor.schedule(callable, delay, TimeUnit.SECONDS);
//以固定周期频率执行任务
executor.scheduleAtFixedRate(runnable, initialdelay,period ,TimeUnit.SECONDS);
//执行固定延迟时间定时任务
service.scheduleWithFixedDelay(runnable, 1,1, TimeUnit.SECONDS);

推荐使用ThreadPoolExecutor创建线程池

ExecutorService extends Executor ThreadPoolExecutor继承自ExecutorService

        /**
         * 推荐ThreadPoolExecutor创建线程池,可以自定义传入我们设置的线程池的参数,更加灵活
         * 上面的所有创建线程的方法最终都是创建ThreadPoolExecutor实现
         * 实现定时任务都是创建ScheduledThreadPoolExecutor,传入了一个DelayedWorkQueue队列而已
         *
         * corePoolSize:在线程池中保持的线程的数量
         * maximumPoolSize:最大线程数量,当workQueue队列已满,放不下新的任务,再通过execute添加新的任务则线程池会再创建新的线程,线程数量大于corePoolSize但不会超过maximumPoolSize
         * keepAliveTime:线程的空闲时间大于keepAliveTime,则该线程会被销毁
         * unit:计时单位
         * workQueue:阻塞队列,当线程池正在运行的线程数量已经达到corePoolSize,那么再通过execute添加新的任务则会被加到workQueue队列中,在队列中排队等待执行,而不会立即执行,还有其他队列:ArrayBlockingQueue(20)
         * threadFactory:线程工厂类
         */
        Executor myThread=new ThreadPoolExecutor(100,100,0L, TimeUnit.SECONDS,new PriorityBlockingQueue(),Executors.defaultThreadFactory());

        // 基于ThreadPoolExecutor的定时任务线程
        ScheduledThreadPoolExecutor executor  = new ScheduledThreadPoolExecutor(1);

其他定时任务

quartz

  • 调度器:Scheduler

  • 任务:JobDetail

  • 触发器:Trigger,包括SimpleTrigger和CronTrigger

依赖:org.quartz-scheduler quartz 2.3.2

定义执行任务job:public class PrintWordsJob implements Job{

@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
    System.out.println("PrintWordsJob start at:" + printTime + ", prints: Hello Job-" + new Random().nextInt(100));

}

}

创建Schedule,执行任务:public class MyScheduler {

public static void main(String[] args) throws SchedulerException, InterruptedException {
    // 1、创建调度器Scheduler
    SchedulerFactory schedulerFactory = new StdSchedulerFactory();
    Scheduler scheduler = schedulerFactory.getScheduler();
    // 2、创建JobDetail实例,并与PrintWordsJob类绑定(Job执行内容)
    JobDetail jobDetail = JobBuilder.newJob(PrintWordsJob.class)
                                    .withIdentity("job1", "group1").build();
    // 3、构建Trigger实例,每隔1s执行一次
    Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
            .startNow()//立即生效
            .withSchedule(SimpleScheduleBuilder.simpleSchedule()
            .withIntervalInSeconds(1)//每隔1s执行一次
            .repeatForever()).build();//一直执行

    //4、执行
    scheduler.scheduleJob(jobDetail, trigger);
    System.out.println("--------scheduler start ! ------------");
    scheduler.start();

    //睡眠
    TimeUnit.MINUTES.sleep(1);
    scheduler.shutdown();
    System.out.println("--------scheduler shutdown ! ------------");


}

}


 

/   总结   /

 

本文主要讲了定时任务的几大类和使用方式,其实还有好多情形都需要我们去考虑,多线程定时任务要不要考虑同步加锁问题,要不要考虑线程安全问题,要不要考虑系统负载的问题,等等等,在以后的文章中我们会陆续讲到。

 

往期推荐:

如何入门做软件开发

为什么我不推荐入行程序员

做全栈开发很难吗

关注我的公众号,学习技术或投稿

长按上图,识别图中二维码即可关注

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Spring Boot中,可以使用@Scheduled注解来实现定时任务,可以结合@Async注解来实现多线程异步。 首先,需要在启动类上添加@EnableAsync注解,开启异步支持。然后在要执行异步任务的方法上添加@Async注解。 接下来,可以使用Java中的Executor框架来创建线程池,用于执行异步任务。可以在应用程序中创建一个线程池,并使用@Async注解将任务提交给线程池执行。 下面是一个示例代码: ```java @Configuration @EnableAsync public class AsyncConfig { @Bean(name = "taskExecutor") public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(20); executor.setQueueCapacity(30); executor.setThreadNamePrefix("MyAsyncThread-"); executor.initialize(); return executor; } } @Service public class MyService { @Async("taskExecutor") @Scheduled(cron = "0 0 12 * * ?") //每天中午12点执行 public void myAsyncTask() { //异步任务内容 } } ``` 在上面的示例中,我们创建了一个名为“taskExecutor”的线程池,并将其注入到MyService中的myAsyncTask方法中。该方法使用@Async注解来指示它应该在异步线程中执行。@Scheduled注解指定了任务执行的时间。 需要注意的是,@Async注解只有在调用该方法的类通过Spring容器进行管理时才会生效。如果通过new关键字手动创建对象,@Async注解将不起作用。 希望这可以帮助你完成Spring Boot定时任务整合多线程异步的实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值