精华内容
下载资源
问答
  • 如何使用?用法1、需要定时执行的方法上加上@Scheduled注解,这个注解中可以指定...案例db中有很多需要推送的任务,然后将其检索出来,推送到手机端,来个定时器一次从库中检测需要推送的消息,然后推送到手机...

    如何使用?

    用法

    1、需要定时执行的方法上加上@Scheduled注解,这个注解中可以指定定时执行的规则,稍后详细介绍。

    2、Spring容器中使用@EnableScheduling开启定时任务的执行,此时spring容器才可以识别@Scheduled标注的方法,然后自动定时执行。

    案例

    db中有很多需要推送的任务,然后将其检索出来,推送到手机端,来个定时器,每秒一次从库中检测需要推送的消息,然后推送到手机端。

    package com.javacode2018.scheduled.demo1;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;@Componentpublic class PushJob {    //推送方法,每秒执行一次    @Scheduled(fixedRate = 1000)    public void push() throws InterruptedException {        System.out.println("模拟推送消息," + System.currentTimeMillis());    }}

    来个spring配置类,需要使用@EnableScheduling标注

    package com.javacode2018.scheduled.demo1;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.scheduling.annotation.EnableScheduling;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;@ComponentScan@EnableScheduling //在spring容器中启用定时任务的执行public class MainConfig1 {    @Bean    public ScheduledExecutorService scheduledExecutorService() {        return Executors.newScheduledThreadPool(20);    }}

    测试类

    package com.javacode2018.scheduled;import com.javacode2018.scheduled.demo1.MainConfig1;import org.junit.Test;import org.springframework.context.annotation.AnnotationConfigApplicationContext;import java.util.concurrent.TimeUnit;public class ScheduledTest {    @Test    public void test1() throws InterruptedException {        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();        context.register(MainConfig1.class);        context.refresh();        //休眠一段时间,房子junit自动退出        TimeUnit.SECONDS.sleep(10000);    }}

    运行输出,每秒会输出一次,如下:

    模拟推送消息,1595840822998模拟推送消息,1595840823998模拟推送消息,1595840824998模拟推送消息,1595840825998模拟推送消息,1595840826998模拟推送消息,1595840827998模拟推送消息,1595840828998

    @Scheduled配置定时规则

    @Scheduled可以用来配置定时器的执行规则,非常强大,@Scheduled中主要有8个参数,我们一一来了解一下。

    @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Repeatable(Schedules.class)public @interface Scheduled { String cron() default ""; String zone() default ""; long fixedDelay() default -1; String fixedDelayString() default ""; long fixedRate() default -1; String fixedRateString() default ""; long initialDelay() default -1; String initialDelayString() default "";}

    1. cron

    该参数接收一个cron表达式,cron表达式是一个字符串,字符串以5或6个空格隔开,分开共6或7个域,每一个域代表一个含义。

    cron表达式语法

    [秒] [分] [小时] [日] [月] [周] [年]

    注:[年]不是必须的域,可以省略[年],则一共6个域

    序号说明必填允许填写的值允许的通配符1秒是0-59, - * /2分是0-59, - * /3时是0-23, - * /4日是1-31, - * ? / L W5月是1-12 / JAN-DEC, - * /6周是1-7 or SUN-SAT, - * ? / L #7年否1970-2099, - * /

    通配符说明:
    • * 表示所有值。例如:在分的字段上设置 *,表示每一分钟都会触发。
    • ? 表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为”?” 具体设置为 0 0 0 10 * ?
    • - 表示区间。例如 在小时上设置 “10-12”,表示 10,11,12点都会触发。
    • , 表示指定多个值,例如在周字段上设置 “MON,WED,FRI” 表示周一,周三和周五触发
    • / 用于递增触发。如在面上面设置”5/15” 表示从5秒开始,每增15秒触发(5,20,35,50)。在日字段上设置’1/3’所示每月1号开始,每隔三天触发一次。
    • L 表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是闰年[leap]), 在周字段上表示星期六,相当于”7”或”SAT”。如果在”L”前加上数字,则表示该数据的最后一个。例如在周字段上设置”6L”这样的格式,则表示“本月最后一个星期五”
    • W 表示离指定日期的最近那个工作日(周一至周五). 例如在日字段上置”15W”,表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发.如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 “1W”,它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,”W”前只能设置具体的数字,不允许区间”-“)。
    • # 序号(表示每月的第几个周几),例如在周字段上设置”6#3”表示在每月的第三个周六.注意如果指定”#5”,正好第五周没有周六,则不会触发该配置(用在母亲节和父亲节再合适不过了) ;小提示:’L’和 ‘W’可以一组合使用。如果在日字段上设置”LW”,则表示在本月的最后一个工作日触发;周字段的设置,若使用英文字母是不区分大小写的,即MON与mon相同。
    示例

    每隔5秒执行一次:*/5 * * * * ?

    每隔1分钟执行一次:0 */1 * * * ?

    每天23点执行一次:0 0 23 * * ?

    每天凌晨1点执行一次:0 0 1 * * ?

    每月1号凌晨1点执行一次:0 0 1 1 * ?

    每月最后一天23点执行一次:0 0 23 L * ?

    每周星期六凌晨1点实行一次:0 0 1 ? * L

    在26分、29分、33分执行一次:0 26,29,33 * * * ?

    每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?

    cron表达式使用占位符

    另外,cron属性接收的cron表达式支持占位符。

    如:配置文件:

    time:  cron: */5 * * * * *  interval: 5

    每5秒执行一次:

    @Scheduled(cron="${time.cron}")void testPlaceholder1() {    System.out.println("Execute at " + System.currentTimeMillis());}@Scheduled(cron="*/${time.interval} * * * * *")void testPlaceholder2() {    System.out.println("Execute at " + System.currentTimeMillis());}

    2. zone

    时区,接收一个java.util.TimeZone#ID。cron表达式会基于该时区解析。默认是一个空字符串,即取服务器所在地的时区。比如我们一般使用的时区Asia/Shanghai。该字段我们一般留空。

    3. fixedDelay

    上一次执行完毕时间点之后多长时间再执行。

    如:

    @Scheduled(fixedDelay = 5000) //上一次执行完毕时间点之后5秒再执行

    4. fixedDelayString

    与 3. fixedDelay 意思相同,只是使用字符串的形式。唯一不同的是支持占位符。

    如:

    @Scheduled(fixedDelayString = "5000") //上一次执行完毕时间点之后5秒再执行

    占位符的使用(配置文件中有配置:time.fixedDelay=5000)

    @Scheduled(fixedDelayString = "${time.fixedDelay}")void testFixedDelayString() {    System.out.println("Execute at " + System.currentTimeMillis());}

    5. fixedRate

    上一次开始执行时间点之后多长时间再执行。

    如:

    @Scheduled(fixedRate = 5000) //上一次开始执行时间点之后5秒再执行

    6. fixedRateString

    与 fixedRate 意思相同,只是使用字符串的形式,唯一不同的是支持占位符。

    7. initialDelay

    第一次延迟多长时间后再执行。

    如:

    @Scheduled(initialDelay=1000, fixedRate=5000) //第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次

    8. initialDelayString

    与 initialDelay 意思相同,只是使用字符串的形式,唯一不同的是支持占位符。

    @Schedules注解

    这个注解不用多解释,看一下源码就知道作用了,当一个方法上面需要同时制定多个定时规则的时候,可以通过这个来配置

    @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Schedules { Scheduled[] value();}

    如:

    //2个定时器,500毫秒的,1000毫秒的@Schedules({@Scheduled(fixedRate = 500), @Scheduled(fixedRate = 1000)})public void push3() {}

    为定时器定义线程池

    定时器默认情况下使用下面的线程池来执行定时任务的

    new ScheduledThreadPoolExecutor(1)

    只有一个线程,相当于只有一个干活的人,如果需要定时执行的任务太多,这些任务只能排队执行,会出现什么问题?

    如果有些任务耗时比较长,导致其他任务排队时间比较长,不能有效的正常执行,直接影响到业务。

    看下面代码,2个方法,都使用了@Scheduled(fixedRate = 1000),表示每秒执行一次,而push1方法中模拟耗时2秒,方法会中打印出线程名称、时间等信息,一会注意观察输出

    package com.javacode2018.scheduled.demo2;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;@Componentpublic class PushJob {    //推送方法,每秒执行一次    @Scheduled(fixedRate = 1000)    public void push1() throws InterruptedException {        //休眠2秒,模拟耗时操作        TimeUnit.SECONDS.sleep(2);        System.out.println(Thread.currentThread().getName() + " push1 模拟推送消息," + System.currentTimeMillis());    }    //推送方法,每秒执行一次    @Scheduled(fixedRate = 1000)    public void push2() {        System.out.println(Thread.currentThread().getName() + " push2 模拟推送消息," + System.currentTimeMillis());    }}

    运行输出

    pool-1-thread-1 push1 模拟推送消息,1595902615507pool-1-thread-1 push2 模拟推送消息,1595902615507pool-1-thread-1 push1 模拟推送消息,1595902617507pool-1-thread-1 push2 模拟推送消息,1595902617507pool-1-thread-1 push1 模拟推送消息,1595902619508pool-1-thread-1 push2 模拟推送消息,1595902619508

    注意上面的输出,线程名称都是pool-1-thread-1,并且有个问题,push2中2次输出时间间隔是2秒,这就是由于线程池中只有一个线程导致了排队执行而产生的问题。

    可以通过自定义定时器中的线程池来解决这个问题,定义一个ScheduledExecutorService类型的bean,名称为taskScheduler

    @Beanpublic ScheduledExecutorService taskScheduler() {    //设置需要并行执行的任务数量    int corePoolSize = 20;    return new ScheduledThreadPoolExecutor(corePoolSize);}

    此时问题就解决了,再次运行一下上面案例代码,结果如下,此时线程名称不一样了,且push2运行正常了

    pool-1-thread-2 push2 模拟推送消息,1595903154636pool-1-thread-2 push2 模拟推送消息,1595903155636pool-1-thread-1 push1 模拟推送消息,1595903156636pool-1-thread-3 push2 模拟推送消息,1595903156636pool-1-thread-1 push2 模拟推送消息,1595903157636

    源码 & 原理

    从EnableScheduling注解开始看,这个注解会导入SchedulingConfiguration类

    26a6941d189f7f0e8bf05f9cc230872e.png

    SchedulingConfiguration是一个配置类,内部定义了ScheduledAnnotationBeanPostProcessor类型的bean

    4b001bba20c3f205d6caf89d64288866.png

    ScheduledAnnotationBeanPostProcessor是一个bean后置处理器,内部有个postProcessAfterInitialization方法,spring中任何bean在初始化完毕之后,会自动调用postProcessAfterInitialization方法,而ScheduledAnnotationBeanPostProcessor在这个方法中会解析bean中标注有@Scheduled注解的方法,这些方法也就是需要定时执行的方法。

    ScheduledAnnotationBeanPostProcessor还实现了一个接口:SmartInitializingSingleton,SmartInitializingSingleton中有个方法afterSingletonsInstantiated会在spring容器中所有单例bean初始化完毕之后调用,定期器的装配及启动都是在这个方法中进行的。

    org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor#afterSingletonsInstantiated

    觉得有帮助的话,记得关注+转发,私聊我有惊喜!

    展开全文
  • 背景 jmeter并发测试个请求之间是没有延时的,但实际工作有时候需要增加固定时间来测试,那么可以Jmeter定器组件,完成工作。其实这些知识百度查就出来,为什么还要写,主要方便自己复习,要找的时候就从自己...

    背景

             jmeter并发测试每个请求之间是没有延时的,但实际工作有时候需要增加固定时间来测试,那么可以Jmeter定器组件,完成工作。其实这些知识百度一查就出来,为什么还要写,主要方便自己复习,要找的时候就从自己文章中找出,而且这些自己通过自己调试后,直接可以拿出来用。

    定时器

    • Constant Timer 固定定时器
    • Uniform Random Timer 均匀随机定时器
    • Constant Throughput Timer 固定吞吐量定时器
    • Gaussian Random Timer 高斯随机定时器
    • JSR223 Timer JSR223定时器
    • Poisson Random Timer 泊松随机定时器
    • Synchronizing Timer 同步定时器
    • BeanShell Timer BeanShell脚本编写定时器
    • Precise Throughput Timer 精准吞吐量定时器

    截图:

    6b2638af3cc83f07f7759ae340abad43.png

    1、Constant Timer 固定定时器

    1c238b81ef04ea3e719421e4250a5372.png

         作用:通过ThreadDelay设定每个线程请求之前的等待时间(单位为毫秒)。

    2、Uniform Random Timer 均匀随机定时器

    e3f0febd1fb4a72c3f4ff9f4c0bfd588.png

    作用:它产生的延迟时间是个随机值,而各随机值出现的概率均等。总的延迟时间等于一个随机延迟时间加上一个固定延迟时间,用户可以设置随机延迟时间和固定延迟时间。总延迟时间=指定范围内的随机时间+固定延迟时间

    3、Constant Throughput Timer 固定吞吐量定时器

    62a2e5f4ad89e7ea8a64c9edee876a68.png

    作用:  按指定的吞吐量执行,以每分钟为单位。计算吞吐量依据是最后一次线程的执行时延。Target throughput(in samples per minute):目标吞吐量。注意这里是每分钟发送的请求数,可以选择作用的线程:当前线程、当前线程组、所有线程组等,具体含义如下:
    • this thread only: 设置每个线程的吞吐量。总的吞吐量=线程数*该值。
    • all active threads in current thread group:吞吐量被分摊到当前线程组所有的活动线程上。每个线程将根据上次运行时间延迟。
    • all active threads:吞吐量被分配到所有线程组的所有活动线程的总吞吐量。每个线程将根据上次运行时间延迟。在这种情况下,每个线程组需要一个具有相同设置的固定吞吐量定时器。(不常用)
    • all active threads in current thread group (shared):同上,但是每个线程是根据组中的线程的上一次运行时间来延迟。相当于线程组组内排队。(不常用)
    • all active threads (shared):同上,但每个线程是根据线程的上次运行时间来延迟。相当于让所有线程组整体排队。(不常用)

    4、Gaussian Random Timer 高斯随机定时器

    feaf5b22e04c63c5a125b302e6af3f69.png

    作用:每个线程的延迟时间是符合标准正态分布的随机时间停顿,那么使用这个定时器,总延迟 = 高斯分布值(平均0.0和标准偏差1.0)* 指定的偏差值+固定延迟偏移(Math.abs((this.random.nextGaussian() * 偏差值) + 固定延迟偏移))

    5、JSR223 Timer JSR223定时器

    b946d36493a2dcce5dba8808ca038136.png

    JSR223计时器可以使用JSR223脚本语言生成延迟;

    参考帮助文档:

    https://jmeter.apache.org/usermanual/component_reference.html#JSR223_Timer

    6、Poisson Random Timer 泊松随机定时器

    70be64b5ee4fc616c2fcf4a6d5ddbed8.png

    这个定时器在每个线程请求之前按随机的时间停顿,总的延迟就是泊松分布值和偏移值之和。上面表示暂停时间会分布在100到400毫秒之间:(1)Lambda(in milliseconds):兰布达值(2)Constant Delay Offset(in milliseconds):暂停的毫秒数减去随机延迟的毫秒数

    7、Synchronizing Timer 同步定时器

    18ef91e6dae4ee1ea54e951ce5ff3ee3.png

    作用:用来设置集合点,其作用是:阻塞线程,直到指定的线程数量到达后,再一起释放,可以瞬间产生很大的压力(1)Number of Simulated Users to Group by:模拟用户的数量,即指定同时释放的线程数数量,若设置为0,等于设置为线程组中的线程数量;(2)Timeout in milliseconds:超时时间,即超时多少毫秒后同时释放指定的线程数;如果设置为0,该定时器将会等待线程数达到了设置的线程数才释放,若没有达到设置的线程数会一直死等。如果大于0,那么如果超过Timeout inmilliseconds中设置的最大等待时间后还没达到设置的线程数,Timer将不再等待,释放已到达的线程。默认为0同步定时器(Synchronizing Timer)的超时时间设置要求:超时时间 > 请求集合数量 * 1000 / (线程数 / 线程加载时间)

    8、BeanShell Timer BeanShell脚本编写定时器

    f79cf362025fe30122318f58c431761f.png

    参数说明:

    • Reset Interpreter:每次迭代是否重置解析器,默认为false;在长时间运行的脚本中建议设置为true。
    • Parameters:BeanShell脚本的入参。入参可以是单个变量;也可以是数组,若是字符串数组,两个元素之间用空格隔开;也可以是常量。
    • File Name:BeanShell脚本可以从脚本文件中读取。
    • Script:在Script区直接写BeanShell脚本。

    简单写一demo增加一个sleep等待一分钟:

    58d3bab8c92cbeddc3bd060a96f394ea.png

      try {            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }

    增加一个Java Request请求,并且增加时间验证是否按照自己设定的定时运行脚本: 

    Java Request ${__time(yyyy-MM-dd HH:mm:ss:SSS,)}

    95504b6ab4c9be4bbec6caa6d1b10413.png

    增加结果查看树:

    fc30911e7b513b886f5793955948d33f.png

    结果显示按之前设置的每个1秒钟运行

    9、Precise Throughput Timer 精准吞吐量定时器

    bbd8b8e068eadf935e6bf9de01fc71fa.png

    • Target Throught:目标吞吐量

    • Throught Period:表示在多长时间内发送Target Throught指定的请求数(以秒为单位)

    • Test Druation:指定测试运行时间(以秒为单位)

    • Number of threads in the bath:用来设置集合点,等到指定个数的请求后并发执行其它参数默认即可。

                                        《劝学》

                                        作者:荀子

    青,取之于蓝,而青于蓝;冰,水为之,而寒于水。木直中绳,輮以为轮,其曲中规。虽有槁暴,不复挺者,輮使之然也。故木受绳则直,金就砺则利,君子博学而日参省乎己,则知明而行无过矣。 

    0c97db47128abbce8ccd914d5e9d5681.png

    展开全文
  • 描述最近在公司部署crontab的时候,突发奇想是否可以用PHP去实现定时器,颗粒度到秒级就好,因为crontab最多到分钟级别,同时也调研了一下用PHP去实现的定时器还真不太多,Swoole 扩展里面到实现了个毫秒级的...

    描述

    最近在公司部署crontab的时候,突发奇想是否可以用PHP去实现一个定时器,颗粒度到秒级就好,因为crontab最多到分钟级别,同时也调研了一下用PHP去实现的定时器还真不太多,Swoole 扩展里面到实现了一个毫秒级的定时器很高效,后面写一篇,先用PHP去实现一个定时器类,以供学习参考。

    实现

    在实现定时器代码的时候,用到了PHP系统自带的两个扩展

    Pcntl - 多进程扩展 :

    主要就是让PHP可以同时开启很多子进程,并行的去处理一些任务。

    Spl - SplMinHeap - 小顶堆

    一个小顶堆数据结构,在实现定时器的时候,采用这种结构效率还是不错的,插入、删除的时间复杂度都是 O(logN) ,像 libevent 的定时器也在 1.4 版本以后采用了这种数据结构之前用的是 rbtree,如果要是使用链表或者固定的数组,每次插入、删除可能都需要重新遍历或者排序,还是有一定的性能问题的。

    流程

    e860316896c7495bfb67bb75f7097d04.png

    说明

    1、定义定时器结构,有什么参数之类的.
    2、然后全部注册进我们的定时器类 Timer.
    3、调用定时器类的monitor方法,开始进行监听.
    4、监听过程就是一个while死循环,不断的去看时间堆的堆顶是否到期了,本来考虑每秒循环看一次,后来一想每秒循环看一次还是有点问题,如果正好在我们sleep(1)的时候定时器有到期的了,那我们就不能马上去精准执行,可能会有延时的风险,所以还是采用 usleep(1000) 毫秒级的去看并且也可以将进程挂起减轻 CPU 负载.

    代码

    /***
    * Class Timer
    */
    class Timer extends SplMinHeap
    {
      /**
      * 比较根节点和新插入节点大小
      * @param mixed $value1
      * @param mixed $value2
      * @return int
      */
      protected function compare($value1, $value2)
      {
        if ($value1['timeout'] > $value2['timeout']) {
          return -1;
        }
        if ($value1['timeout'] < $value2['timeout']) {
          return 1;
        }
        return 0;
      }
      /**
      * 插入节点
      * @param mixed $value
      */
      public function insert($value)
      {
        $value['timeout'] = time() + $value['expire'];
        parent::insert($value);
      }
      /**
      * 监听
      * @param bool $debug
      */
      public function monitor($debug = false)
      {
        while (!$this->isEmpty()) {
          $this->exec($debug);
          usleep(1000);
        }
      }
      /**
      * 执行
      * @param $debug
      */
      private function exec($debug)
      {
        $hit = 0;
        $t1  = microtime(true);
        while (!$this->isEmpty()) {
          $node = $this->top();
          if ($node['timeout'] <= time()) {
            //出堆或入堆
            $node['repeat'] ? $this->insert($this->extract()) : $this->extract();
            $hit = 1;
            //开启子进程
            if (pcntl_fork() == 0) {
              empty($node['action']) ? '' : call_user_func($node['action']);
              exit(0);
            }
            //忽略子进程,子进程退出由系统回收
            pcntl_signal(SIGCLD, SIG_IGN);
          } else {
            break;
          }
        }
        $t2 = microtime(true);
        echo ($debug && $hit) ? '时间堆 - 调整耗时: ' . round($t2 - $t1, 3) . "秒rn" : '';
      }
    }

    实例

    $timer = new Timer();
    //注册 - 3s - 重复触发
    $timer->insert(array('expire' => 3, 'repeat' => true, 'action' => function(){
      echo '3秒 - 重复 - hello world' . "rn";
    }));
    //注册 - 3s - 重复触发
    $timer->insert(array('expire' => 3, 'repeat' => true, 'action' => function(){
      echo '3秒 - 重复 - gogo' . "rn";
    }));
    //注册 - 6s - 触发一次
    $timer->insert(array('expire' => 6, 'repeat' => false, 'action' => function(){
      echo '6秒 - 一次 - hello xxxx' . "rn";
    }));
    //监听
    $timer->monitor(false);

    执行结果

    829a5318bac102f5ca8d5b78a26185a8.png

    也测试过比较极端的情况,同时1000个定时器1s全部到期,时间堆全部调整完仅需 0.126s 这是没问题的,但是每调整完一个定时器就需要去开启一个子进程,这块可能比较耗时了,有可能1s处理不完这1000个,就会影响下次监听继续触发,但是不开启子进程,比如直接执行应该还是可以处理完的。。。。当然肯定有更好的方法,目前只能想到这样。

    以上内容希望帮助到大家,很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要戳这里PHP进阶架构师>>>视频、面试文档免费获取

    或 者关注我每天分享技术文章

    PHP进阶编程www.zhihu.com
    57019608bb4287a24aef47ec5ef033ad.png
    来源:https://zhuanlan.zhihu.com/p/94679598
    展开全文
  • 跑了几家大的公司面试,现在在家等面试结果,应该可以拿到几个比较好的offer,就这几家面试而言,我整理了份复习用的面试题及面试高频的考点题及技术点梳理成一份“Java程序员高频面试解析及知识点体系笔记.pdf...
    352abe9e1dd6ef2db2c248711986b869.png

    就目前大环境来看,跳槽成功的难度比往年高很多。总结一下2019面试的感受:无论一面还是二面,都很考验Java程序员的技术功底!!
    跑了几家大的公司面试,现在在家等面试结果,应该可以拿到几个比较好的offer,就这几家面试而言,我整理了一份复习用的面试题及面试高频的考点题及技术点梳理成一份“Java程序员高频面试解析及知识点体系笔记.pdf(实际上比预期多花了不少精力),现在分享给大家。

    79f8116f07c1b83e842de89f391258e9.png
    169799cd4059314a28aec75da29f1e5a.png
    5d691c7fc7c7cd7ba884d25425c95e62.png
    17dec25ecb06fde38f22e57ca53d48b1.png

    事实上目录比较长就不全部发出来了,我后面会陆续更新,大家可以持续关注一下

    6346efdc155f91c742ee271c7f2cf180.png

    我们都知道 Java 源文件,通过编译器,能够生产相应的.Class 文件,也就是字节码文件,

    而字节码文件又通过 Java 虚拟机中的解释器,编译成特定机器上的机器码 。

    也就是如下:

    ① Java 源文件—->编译器—->字节码文件

    ② 字节码文件—->JVM—->机器码

    每一种平台的解释器是不同的,但是实现的虚拟机是相同的,这也就是 Java 为什么能够

    跨平台的原因了 ,当一个程序从开始运行,这时虚拟机就开始实例化了,多个程序启动就会

    存在多个虚拟机实例。程序退出或者关闭,则虚拟机实例消亡,多个虚拟机实例之间数据不

    能共享。

    7cf66200477699224b158b4946501a7d.png

    2.1.线程

    这里所说的线程指程序执行过程中的一个线程实体。JVM 允许一个应用并发执行多个线程。

    Hotspot JVM 中的 Java 线程与原生操作系统线程有直接的映射关系。当线程本地存储、缓

    冲区分配、同步对象、栈、程序计数器等准备好以后,就会创建一个操作系统原生线程。

    Java 线程结束,原生线程随之被回收。操作系统负责调度所有线程,并把它们分配到任何可

    用的 CPU 上。当原生线程初始化完毕,就会调用 Java 线程的 run() 方法。当线程结束时,会释放原生线程和 Java 线程的所有资源。

    Hotspot JVM 后台运行的系统线程主要有下面几个:

    虚拟机线程

    (VM thread)

    这个线程等待 JVM 到达安全点操作出现。这些操作必须要在独立的线程里执行,因为当

    堆修改无法进行时,线程都需要 JVM 位于安全点。这些操作的类型有:stop-the

    world 垃圾回收、线程栈 dump、线程暂停、线程偏向锁(biased locking)解除。

    周期性任务线程

    这线程负责定时器事件(也就是中断),用来调度周期性操作的执行。

    GC 线程

    这些线程支持 JVM 中不同的垃圾回收活动。

    编译器线程

    这些线程在运行时将字节码动态编译成本地平台相关的机器码。

    信号分发线程

    这个线程接收发送到 JVM 的信号并调用适当的 JVM 方法处理。

    2.2.JVM 内存区域

    9100f8d57186e94447a9536ce1c456e7.png

    JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区

    域【JAVA 堆、方法区】、直接内存。

    线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 创建/销毁(在 Hotspot

    VM 内, 每个线程都与操作系统的本地线程直接映射, 因此这部分内存区域的存/否跟随本地线程的

    生/死对应)。

    线程共享区域随虚拟机的启动/关闭而创建/销毁。

    直接内存并不是 JVM 运行时数据区的一部分, 但也会被频繁的使用: 在 JDK 1.4 引入的 NIO 提

    供了基于 Channel 与 Buffer 的 IO 方式, 它可以使用 Native 函数库直接分配堆外内存, 然后使用

    DirectByteBuffer 对象作为这块内存的引用进行操作(详见: Java I/O 扩展), 这样就避免了在 Java

    堆和 Native 堆中来回复制数据, 因此在一些场景中可以显著提高性能。

    cb5643d9102321e151d1a9254885983a.png

    2.2.1. 程序计数器(线程私有)

    一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的

    程序计数器,这类内存也称为“线程私有”的内存。

    正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如

    果还是 Native 方法,则为空。

    这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。

    2.2.2. 虚拟机栈(线程私有)

    是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)

    用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成

    的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

    栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接

    (Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception)。栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异

    常)都算作方法结束。

    2ae3a104e1d5418552962a009c4d12ef.png

    2.2.3. 本地方法区(线程私有)

    本地方法区和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为

    Native 方法服务, 如果一个 VM 实现使用 C-linkage 模型来支持 Native 调用, 那么该栈将会是一个

    C 栈,但 HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一。

    2.2.4. 堆(Heap-线程共享)-运行时数据区

    是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行

    垃圾收集的最重要的内存区域。由于现代 VM 采用分代收集算法, 因此 Java 堆从 GC 的角度还可以

    细分为: 新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年代。

    2.2.5. 方法区/永久代(线程共享)

    即我们常说的永久代(Permanent Generation), 用于存储被 JVM 加载的类信息常量

    态变量即时编译器编译后的代码等数据. HotSpot VM把GC分代收集扩展至方法区, 即使用Java

    堆的永久代来实现方法区, 这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存,

    而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收类型

    的卸载, 因此收益一般很小)。

    运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版

    本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加

    载后存放到方法区的运行时常量池中。 Java 虚拟机对 Class 文件的每一部分(自然也包括常量

    池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会

    被虚拟机认可、装载和执行。

    2.3.JVM 运行时内存

    Java 堆从 GC 的角度还可以细分为: 新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年

    代。

    143d454200c1e4437b7b0c88ab20e456.png

    2.3.1. 新生代

    是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁触发

    MinorGC 进行垃圾回收。新生代又分为 Eden 区、ServivorFrom、ServivorTo 三个区。

    2.3.1.1.

    Eden 区

    Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老

    年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行

    一次垃圾回收。

    2.3.1.2.

    ServivorFrom

    上一次 GC 的幸存者,作为这一次 GC 的被扫描者。

    2.3.1.3.

    ServivorTo

    保留了一次 MinorGC 过程中的幸存者。

    2.3.1.4. MinorGC 的过程(复制->清空->互换)

    MinorGC 采用复制算法。

    1:eden、servicorFrom 复制到 ServicorTo,年龄+1

    首先,把 Eden 和 ServivorFrom 区域中存活的对象复制到 ServicorTo 区域(如果有对象的年

    龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果 ServicorTo 不

    够位置了就放到老年区);

    2:清空 eden、servicorFrom

    然后,清空 Eden 和 ServicorFrom 中的对象;

    3:ServicorTo 和 ServicorFrom 互换

    最后,ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom

    区。

    2.3.2. 老年代

    主要存放应用程序中生命周期长的内存对象。

    老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行

    了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足

    够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。

    MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没

    有标记的对象。MajorGC 的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减

    少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的

    时候,就会抛出 OOM(Out of Memory)异常。

    2.3.3. 永久代

    指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被

    放入永久区域,它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。所以这

    也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。

    2.3.3.1. JAVA8 与元数据

    在 Java8 中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间

    的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用

    本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native

    memory, 字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由

    MaxPermSize 控制, 而由系统的实际可用空间来控制。

    b7f5e1758885cf6a6d2b34716dc4b8c3.png

    2.4.1. 如何确定垃圾

    2.4.1.1.

    引用计数法

    在 Java 中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单

    的办法是通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之关

    联的引用,即他们的引用计数都不为 0,则说明对象不太可能再被用到,那么这个对象就是可回收

    对象。

    2.4.1.2.

    可达性分析

    为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的“GC roots”

    对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。

    要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记

    过程。两次标记后仍然是可回收对象,则将面临回收。

    2.4.2. 标记清除算法(Mark-Sweep)

    最基础的垃圾回收算法,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清

    除阶段回收被标记的对象所占用的空间。如图

    a0ce15e0ea1ad613370c9796eed2207e.png

    从图中我们就可以发现,该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可

    利用空间的问题。

    2.4.3. 复制算法(copying)

    为了解决 Mark-Sweep 算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小

    的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用

    的内存清掉,如图:

    e4b17d7a06c66ef6561fa2270195279c.png

    这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原

    本的一半。且存活对象增多的话,Copying 算法的效率会大大降低。

    2.4.4. 标记整理算法(Mark-Compact)

    结合了以上两个算法,为了避免缺陷而提出。标记阶段和 Mark-Sweep 算法相同,标记后不是清

    理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。如图:

    51a72784d199c3d40d339b9ae29d1992.png

    2.4.5. 分代收集算法

    分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存

    划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young

    Generation)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃

    圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。

    2.4.5.1. 新生代与复制算法

    目前大部分 JVM 的 GC 对于新生代都采取 Copying 算法,因为新生代中每次垃圾回收都要

    回收大部分对象,即要复制的操作比较少,但通常并不是按照 1:1 来划分新生代。一般将新生代

    划分为一块较大的 Eden 空间和两个较小的 Survivor 空间(From Space, To Space),每次使用

    Eden 空间和其中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另

    一块 Survivor 空间中。

    3dadb935ccf6da9ceee60501396783b6.png

    2.4.5.2. 老年代与标记复制算法

    而老年代因为每次只回收少量对象,因而采用 Mark-Compact 算法。

    1. JAVA 虚拟机提到过的处于方法区的永生代(Permanet Generation),它用来存储 class 类,

    常量,方法描述等。对永生代的回收主要包括废弃常量和无用的类。

    2. 对象的内存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Survivor 目

    前存放对象的那一块),少数情况会直接分配到老生代。

    3. 当新生代的 Eden Space 和 From Space 空间不足时就会发生一次 GC,进行 GC 后,Eden

    Space 和 From Space 区的存活对象会被挪到 To Space,然后将 Eden Space 和 From

    Space 进行清理。

    4. 如果 To Space 无法足够存储某个对象,则将这个对象存储到老生代。

    5. 在进行 GC 后,使用的便是 Eden Space 和 To Space 了,如此反复循环。

    6. 当对象在 Survivor 区躲过一次 GC 后,其年龄就会+1。默认情况下年龄到达 15 的对象会被

    移到老生代中。13/04/2018

    Page 30 of 283

    2.5.JAVA 四中引用类型

    2.5.1. 强引用

    在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引

    用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即

    使该对象以后永远都不会被用到 JVM 也不会回收。因此强引用是造成 Java 内存泄漏的主要原因之

    一。

    2.5.2. 软引用

    软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它

    不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。

    2.5.3. 弱引用

    弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象

    来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存。

    2.5.4. 虚引用

    虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚

    引用的主要作用是跟踪对象被垃圾回收的状态。

    2.6.GC 分代收集算法 VS 分区收集算法

    2.6.1. 分代收集算法

    当前主流 VM 垃圾收集都采用”分代收集”(Generational Collection)算法, 这种算法会根据

    对象存活周期的不同将内存划分为几块, 如 JVM 中的 新生代、老年代、永久代,这样就可以根据

    各年代特点分别采用最适当的 GC 算法

    2.6.1.1. 在新生代-复制算法

    每次垃圾收集都能发现大批对象已死, 只有少量存活. 因此选用复制算法, 只需要付出少量

    存活对象的复制成本就可以完成收集.

    2.6.1.2. 在老年代-标记整理算法

    因为对象存活率高、没有额外空间对它进行分配担保, 就必须采用“标记—清理”或“标

    记—整理”算法来进行回收, 不必进行内存复制, 且直接腾出空闲内存.2.6.2. 分区收集算法

    分区算法则将整个堆空间划分为连续的不同小区间, 每个小区间独立使用, 独立回收. 这样做的

    好处是可以控制一次回收多少个小区间 , 根据目标停顿时间, 每次合理地回收若干个小区间(而不是

    整个堆), 从而减少一次 GC 所产生的停顿。

    2.7.GC 垃圾收集器

    Java 堆内存被划分为新生代和年老代两部分,新生代主要使用复制和标记-清除垃圾回收算法;

    年老代主要使用标记-整理垃圾回收算法,因此 java 虚拟中针对新生代和年老代分别提供了多种不

    同的垃圾收集器,JDK1.6 中 Sun HotSpot 虚拟机的垃圾收集器如下:

    425c880f14f144d1108fafa7b2ee4a89.png

    2.7.1. Serial 垃圾收集器(单线程、复制算法)

    Serial(英文连续)是最基本垃圾收集器,使用复制算法,曾经是JDK1.3.1 之前新生代唯一的垃圾

    收集器。Serial 是一个单线程的收集器,它不但只会使用一个 CPU 或一条线程去完成垃圾收集工

    作,并且在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。

    Serial 垃圾收集器虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限

    定单个 CPU 环境来说,没有线程交互的开销,可以获得最高的单线程垃圾收集效率,因此 Serial

    垃圾收集器依然是 java 虚拟机运行在 Client 模式下默认的新生代垃圾收集器。

    2.7.2. ParNew 垃圾收集器(Serial+多线程)

    ParNew 垃圾收集器其实是 Serial 收集器的多线程版本,也使用复制算法,除了使用多线程进行垃

    圾收集之外,其余的行为和 Serial 收集器完全一样,ParNew 垃圾收集器在垃圾收集过程中同样也

    要暂停所有其他的工作线程。

    ParNew 收集器默认开启和 CPU 数目相同的线程数,可以通过-XX:ParallelGCThreads 参数来限

    制垃圾收集器的线程数。【Parallel:平行的】

    ParNew虽然是除了多线程外和Serial 收集器几乎完全一样,但是ParNew垃圾收集器是很多 java

    虚拟机运行在 Server 模式下新生代的默认垃圾收集器。

    2.7.3. Parallel Scavenge 收集器(多线程复制算法、高效)

    Parallel Scavenge 收集器也是一个新生代垃圾收集器,同样使用复制算法,也是一个多线程的垃

    圾收集器,它重点关注的是程序达到一个可控制的吞吐量(Thoughput,CPU 用于运行用户代码

    的时间/CPU 总消耗时间,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),

    高吞吐量可以最高效率地利用 CPU 时间,尽快地完成程序的运算任务,主要适用于在后台运算而

    不需要太多交互的任务。自适应调节策略也是 ParallelScavenge 收集器与 ParNew 收集器的一个

    重要区别。

    2.7.4. Serial Old 收集器(单线程标记整理算法 )

    Serial Old 是 Serial 垃圾收集器年老代版本,它同样是个单线程的收集器,使用标记-整理算法,

    这个收集器也主要是运行在 Client 默认的 java 虚拟机默认的年老代垃圾收集器。

    在 Server 模式下,主要有两个用途:

    1. 在 JDK1.5 之前版本中与新生代的 Parallel Scavenge 收集器搭配使用。

    2. 作为年老代中使用 CMS 收集器的后备垃圾收集方案。

    新生代 Serial 与年老代 Serial Old 搭配垃圾收集过程图:

    1b922a5372484b84e4be68a7ef78b54f.png

    新生代 Parallel Scavenge 收集器与 ParNew 收集器工作原理类似,都是多线程的收集器,都使

    用的是复制算法,在垃圾收集过程中都需要暂停所有的工作线程。新生代 Parallel

    Scavenge/ParNew 与年老代 Serial Old 搭配垃圾收集过程图:

    46c5adb22541aa5cc460dc4f8c86ecf2.png

    2.7.5. Parallel Old 收集器(多线程标记整理算法)

    Parallel Old 收集器是Parallel Scavenge的年老代版本,使用多线程的标记-整理算法,在 JDK1.6

    才开始提供。

    在 JDK1.6 之前,新生代使用 ParallelScavenge 收集器只能搭配年老代的 Serial Old 收集器,只

    能保证新生代的吞吐量优先,无法保证整体的吞吐量,Parallel Old 正是为了在年老代同样提供吞

    吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,可以优先考虑新生代 Parallel Scavenge

    和年老代 Parallel Old 收集器的搭配策略。

    新生代 Parallel Scavenge 和年老代 Parallel Old 收集器搭配运行过程图:

    f239dcec45cc0b28dac21b31ab772b80.png

    2.7.6. CMS 收集器(多线程标记清除算法)

    Concurrent mark sweep(CMS)收集器是一种年老代垃圾收集器,其最主要目标是获取最短垃圾

    回收停顿时间,和其他年老代使用标记-整理算法不同,它使用多线程的标记-清除算法。

    最短的垃圾收集停顿时间可以为交互比较高的程序提高用户体验。

    CMS 工作机制相比其他的垃圾收集器来说更复杂,整个过程分为以下 4 个阶段:

    2.7.6.1. 初始标记

    只是标记一下 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。

    2.7.6.2. 并发标记

    进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。

    2.7.6.3. 重新标记

    为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记

    记录,仍然需要暂停所有的工作线程。

    2.7.6.4. 并发清除

    清除 GC Roots 不可达对象,和用户线程一起工作,不需要暂停工作线程。由于耗时最长的并

    发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作,所以总体上来看

    CMS 收集器的内存回收和用户线程是一起并发地执行。

    CMS 收集器工作过程:

    2069d55b3c69fa5a5879318bf9f0ba9e.png

    2.7.7. G1 收集器

    Garbage first 垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与 CMS 收集器,G1 收

    集器两个最突出的改进是:

    1. 基于标记-整理算法,不产生内存碎片。

    2. 可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。

    G1 收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域

    的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾

    最多的区域。区域划分和优先级区域回收机制,确保 G1 收集器可以在有限时间获得最高的垃圾收

    集效率。

    因为后面的资料有点长,头条没有办法放太多,今天就先更到这里,如果有兴趣的朋友可以关注一下后续更新,后续将每天一更持续更新,感谢关注哦~如果觉得可以的话,也可以转发给更多的朋友看到,谢谢哟!

    展开全文
  • import java.util.Calendar;...public class DemoTimer {//启动系统定时器隔1分钟/指定时间执行任务//java.util.Timer定时器是以后台线程方式控制运行,它是线程安全,无需手工加锁public s...
  • public static void w(){  System.out.println("123");  }    public static void main(String[] args){ ... Timer timer = new Timer();...//隔5分钟执行一次w()  new java.util.TimerTask() { p
  • 在开发工作中,我们常常会需要一些周期性的操作,比如5分钟执行一次某个程序,又比如定时检查数据库连接池中的连接数,每晚定时备份数据等等,在java中,最方便、最高效的实现方式就是用java.util.Timer工具类,再...
  • 在开发工作中,我们常常会需要一些周期性的操作,比如5分钟执行一次某个程序,又比如定时检查数据库连接池中的连接数,每晚定时备份数据等等,在java中,最方便、最高效的实现方式就是用java.util.Timer工具类,再...
  • Java 定时器(Timer)

    2019-07-08 14:23:30
    在开发中,我们经常需要一些周期性的操作,例如隔几分钟就进行某一项操作。这时候我们就要去设置个定时器Java中最方便、最高效的实现方式...可安排任务执行一次,或者定期重复执行。实际上是个线程,定时调度所...
  • 在开发工作中,我们常常会需要一些周期性的操作,比如5分钟执行一次某个程序,又比如定时检查数据库连接池中的连接数,每晚定时备份数据等等,在java中,最方便、最高效的实现方式就是用java.util.Timer工具类,再...
  • JAVA定时器启动日期

    2016-05-03 09:40:25
    * 个月执行一次 * 参数说明: * day:一个月中的哪一天,默认为当前天 * hour:几点钟 * minute:分钟 * second:秒 默认为00:00:00 */ public static Date getTargetDate(int day, int hour, int minute, ...
  • Java定时器Timer类的使用

    千次阅读 2016-12-11 17:16:56
    在应用开发当中,经常需要一些周期性的操作,比如3分钟执行一次操作等,对于这样的操作最为方便、高效的实现方式就是使用java.util.Timer工具类。  Timer直接从Object继承,它相当于一个计时器,能够用它来制定...
  • 在开发中,我们经常需要一些周期性的操作,例如隔几分钟就进行某一项操作。这时候我们就要去设置个定时器Java中最方便、最高效的实现方式是用java.util...可安排任务执行一次,或者定期重复执行。实际上是个线程...
  • 1分钟触发一次 0 0 * * * ? 每天1小时触发一次 0 0 10 * * ? 每天10点触发一次 0 * 14 * * ? 在每天下午2点到下午2:59期间的1分钟触发 0 30 9 1 * ? 月1号上午9点半执行 0 15 10 15 * ? 月15日上午10:15...
  • JAVA原生定时器Timer

    千次阅读 2018-05-20 11:00:57
    在开发中,我们经常需要一些周期性的操作,例如隔几分钟就进行某一项操作。这时候我们就要去设置个定时器Java中最方便、最高效的实现方式是...可安排任务执行一次,或者定期重复执行。实际上是个线程,定时调度...
  • java 创建定时器_javav

    2021-03-09 06:59:47
    定时器要求:定时执行某存储过程,存储过程实现向某表中插入一条数据每分钟执行一次show variables like '%event_scheduler%';set global event_scheduler=on;create table user01(user_id int ,user_name varchar(3...
  • java使用定时器

    2018-04-16 18:54:05
    在应用开发中,经常需要一些周期性的操作,比如5分钟执行某一操作等。 对于这样的操作最方便、...java编程实例,实时显示当前时间,1秒时钟内容更新一次java源代码:package myproject6; import java.util.Da
  • Java Timer(定时器)

    万次阅读 多人点赞 2018-05-13 00:37:56
    它可以安排任务“执行一次”或者定期“执行多次”。 然而在实际的开发过程当中,经常需要一些周期性的操作,比如5分钟执行某一操作等。 对于这样的操作最方便、高效的实现方式就是使用java.util.Timer工具类。--...
  • 在应用开发中,经常需要一些周期性的操作,比如5分钟执行某一...可安排任务执行一次或者定期重复执行.其中几个方法需要我们注意一下:  cancel():终止此计时器,丢弃所有当前已安排的任务。  schedule(TimerTa
  • @Scheduled 注解 用于定时循环执行任务 例如: ...隔1分钟执行一次:"0 */1 * * * ?" 每天23点执行一次:"0 0 23 * * ?" 每天凌晨1点执行一次:"0 0 1 * * ?" 月1号凌晨1点执行一次:...
  • 一、在spring-cloud(spring-boot)中: 使用spring的定时器,spring自带支持定时器的任务实现。其可通过简单配置来使用到简单的定时任务。 @Component @Configurable ... //1分钟执行一次 @Scheduled...
  • 在开发中,我们经常需要...可安排任务执行一次,或者定期重复执行。实际上是个线程,定时调度所拥有的TimerTasks。 TimerTask是一个抽象类,它的子类由 Timer 安排为一次执行或重复执行的任务。实际上就是一个拥有r
  • 如果需要计划一个任务在指定的时间执行,或者在指定的时间后以指定的间隔连续执行多次,比如希望在2005年1月12号上午11:22:54开始执行一个任务,在这之后隔20分钟执行一次,共执行一次,这种情况下可以使用...
  • 项目中经常使用定时器来执行特定的任务,当单服务器...第一个版本,(单服务部署)用户设置了提醒时间,就往数据库中塞入一条数据,然后定时器每分钟读取一次 数据库,然后将提醒时间读取出来,然后循环遍历去发送消...
  • java定时执行bat文件的cmd命令

    千次阅读 2018-05-07 18:44:43
    需求:实现Windows系统开机后,执行定时器任务,每分钟执行一次指定目录的文件上传下载操作,使我的电脑中工作目录文件和码云git服务器上面的文件保持同步一致package com.kj.test;import java.io.BufferedReader; ...
  • -- 设置的定时器30分钟执行一次 --> * 10/30 * * * ? * </value> <!-- 总管理类 如果将lazy-init='false'那么容器启动就会执行调度程序 --> lazy-init="false" autowire="no"> ...
  • 定时器new Timer().schedule()的使用

    千次阅读 2019-06-05 16:10:32
    在开发中,我们经常需要一些周期性的操作,例如隔几分钟就进行某一项操作。这时候我们就要去设置个定时器Java中最方便...可安排任务执行一次,或者定期重复执行。实际上是个线程,定时调度所拥有的TimerTasks。 ...
  • 需求是定时器每隔90分钟执行一次,暂时有一个解决想法,但是是作为最后备用的:配置定时器每1小时执行,但是在30分钟后运行,运行完后移除定时器再添加回去,感觉不好。
  • 2020-09-08

    2020-09-08 13:36:28
    Java定时器的设置 Timer timer = new Timer(); timer.schedule(new TimerTask(){ public void run() { //要执行的方法 } },10*1000,15*60*1000);//启动后10秒开始执行,往后15分钟执行一次

空空如也

空空如也

1 2
收藏数 33
精华内容 13
关键字:

java定时器每分钟执行一次

java 订阅