草庐IT

Java 定时任务-最简单的3种实现方法

深海呐 2023-04-21 原文

 一、Timer

Timer是JAVA自带的定时任务类,实现如下:

public class MyTimerTask {    
    public static void main(String[] args) {        
        // 定义一个任务       
        TimerTask timerTask = new TimerTask() {            
        @Override            
            public void run() {                
            System.out.println("打印当前时间:" + new Date());    
            }       
         };        
        // 计时器       
        Timer timer = new Timer();       
        // 开始执行任务 (延迟1000毫秒执行,每3000毫秒执行一次)        
        timer.schedule(timerTask, 1000, 3000);    
    }
}

Timer 优缺点分析

优点是使用简单,缺点是当添加并执行多个任务时,前面任务的执行用时和异常将影响到后面任务,这边深海建议谨慎使用。

二、ScheduledExecutorService

ScheduledExecutorService 也是Java自带的类,

它可以实现Timer具备的所有功能,并解决了 Timer类存在的问题

实现如下:

public class MyScheduledExecutorService {    
    public static void main(String[] args) {        
        // 创建任务队列   10 为线程数量      
        ScheduledExecutorService scheduledExecutorService = 
                Executors.newScheduledThreadPool(10); 
        // 执行任务      
        scheduledExecutorService.scheduleAtFixedRate(() -> {          
            System.out.println("打印当前时间:" + new Date());      
        }, 1, 3, TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次   

  }
}

ScheduledExecutorService 优缺点分析

优点是,该类是JDK1.5自带的类,使用简单,缺点是该方案仅适用于单机环境。

三、Spring Task

Spring系列框架中Spring Framework自带的定时任务,

使用上面两种方式,很难实现某些特定需求,比如每周一执行某任务,但SpringTask可轻松实现。

以SpringBoot为例来实现:

1、开启定时任务

在SpringBoot的启动类上声明 @EnableScheduling:

@SpringBootApplication
@EnableScheduling //开启定时任务
public class DemoApplication {  
     // --  -- 
}

2、添加定时任务

只需使用@Scheduled注解标注即可,

如果有多个定时任务,可以创建多个@Scheduled标注的方法,示例如下:

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component // 把此类托管给 Spring,不能省略
public class TaskUtils {    
    // 添加定时任务    
    @Scheduled(cron = "30 40 23 0 0 5") // cron表达式:每周一 23:40:30 执行    
    public void doTask(){        
        System.out.println("我是定时任务~");    
    }
}

 Spring Boot 启动后会自动加载并执行定时任务,无需手动操作。

Cron 表达式

Spring Task 的实现需要使用 cron 表达式来声明执行的频率和规则,cron 表达式是由 6 位或者 7 位组成的(最后一位可以省略),每位之间以空格分隔,每位从左到右代表的含义如下:

其中 * 和 ? 号都表示匹配所有的时间。

cron 表达式在线生成地址:https://cron.qqe2.com/

知识扩展:分布式定时任务

上面的方法都是关于单机定时任务的实现,如果是分布式环境可以使用 Redis 来实现定时任务。

使用 Redis 实现延迟任务的方法大体可分为两类:通过 ZSet 的方式和键空间通知的方式

1、ZSet 实现方式

通过 ZSet 实现定时任务的思路是,将定时任务存放到 ZSet 集合中,并且将过期时间存储到 ZSet 的 Score 字段中,然后通过一个无线循环来判断当前时间内是否有需要执行的定时任务,如果有则进行执行,具体实现代码如下:

import redis.clients.jedis.Jedis;
import utils.JedisUtils;
import java.time.Instant;
import java.util.Set;
public class DelayQueueExample {        
    private static final String _KEY = "DelayQueueExample";        
    public static void main(String[] args) throws InterruptedException {        
        Jedis jedis = JedisUtils.getJedis();        
        // 30s 后执行        
        long delayTime = Instant.now().plusSeconds(30).getEpochSecond();       
        jedis.zadd(_KEY, delayTime, "order_1");        
        // 继续添加测试数据        
       jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_2");       
      jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_3");        
      jedis.zadd(_KEY, Instant.now().plusSeconds(7).getEpochSecond(), "order_4");        
     jedis.zadd(_KEY, Instant.now().plusSeconds(10).getEpochSecond(), "order_5");        
        // 开启定时任务队列        
        doDelayQueue(jedis);    
    }    
    /**     
    * 定时任务队列消费     
    * @param jedis Redis 客户端     
    */    
    public static void doDelayQueue(Jedis jedis) throws InterruptedException {        
        while (true) {            
            // 当前时间            
            Instant nowInstant = Instant.now();            
            long lastSecond = nowInstant.plusSeconds(-1).getEpochSecond(); 
            // 上一秒时间            
            long nowSecond = nowInstant.getEpochSecond();            
            // 查询当前时间的所有任务            
            Set data = jedis.zrangeByScore(_KEY, lastSecond, nowSecond);            
            for (String item : data) {                
            // 消费任务                
            System.out.println("消费:" + item);            
        }            
        // 删除已经执行的任务            
        jedis.zremrangeByScore(_KEY, lastSecond, nowSecond);            
        Thread.sleep(1000); // 每秒查询一次        
        }    
    }
}

2、键空间通知

我们可以通过 Redis 的键空间通知来实现定时任务,它的实现思路是给所有的定时任务设置一个过期时间,等到了过期之后,我们通过订阅过期消息就能感知到定时任务需要被执行了,此时我们执行定时任务即可。

默认情况下 Redis 是不开启键空间通知的,需要我们通过 config set notify-keyspace-events Ex 的命令手动开启,开启之后定时任务的代码如下:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import utils.JedisUtils;
public class TaskExample {    
    public static final String _TOPIC = "__keyevent@0__:expired"; // 订阅频道名称   
    public static void main(String[] args) {       
        Jedis jedis = JedisUtils.getJedis();       
        // 执行定时任务        
        doTask(jedis);    
    }   
     /**     
       * 订阅过期消息,执行定时任务     
       * @param jedis Redis 客户端     
       */    
    public static void doTask(Jedis jedis) {        
        // 订阅过期消息        
        jedis.psubscribe(new JedisPubSub() {            
            @Override            
 public void onPMessage(String pattern, String channel, String message) {                
            // 接收到消息,执行定时任务                
            System.out.println("收到消息:" + message);            
            }            
        }, _TOPIC);    
    }
}

有关Java 定时任务-最简单的3种实现方法的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  5. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  6. Ruby 方法() 方法 - 2

    我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby​​-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco

  7. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  8. ruby - Highline 询问方法不会使用同一行 - 2

    设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案

  9. ruby - 如何使用 RSpec::Core::RakeTask 创建 RSpec Rake 任务? - 2

    如何使用RSpec::Core::RakeTask初始化RSpecRake任务?require'rspec/core/rake_task'RSpec::Core::RakeTask.newdo|t|#whatdoIputinhere?endInitialize函数记录在http://rubydoc.info/github/rspec/rspec-core/RSpec/Core/RakeTask#initialize-instance_method没有很好的记录;它只是说:-(RakeTask)initialize(*args,&task_block)AnewinstanceofRake

  10. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

随机推荐