java Timer(定时调用、实现固定时间执行)

2023-11-09

最近需要用到定时调用的功能。可以通过java的Timer类来进行定时调用,下面是有关Timer的一些相关知识。

其实就Timer来讲就是一个调度器,而TimerTask呢只是一个实现了run方法的一个类,而具体的TimerTask需要由你自己来实现,例如这样:

Timer timer = new Timer();
timer.schedule(new TimerTask() {
        public void run() {
            System.out.println("11232");
        }
}, 200000 , 1000);

这里直接实现一个TimerTask(当然,你可以实现多个TimerTask,多个TimerTask可以被一个Timer会被分配到多个 Timer中被调度,后面会说到Timer的实现机制就是说内部的调度机制),然后编写run方法,20s后开始执行,每秒执行一次,当然你通过一个 timer对象来操作多个timerTask,其实timerTask本身没什么意义,只是和timer集合操作的一个对象,实现它就必然有对应的run 方法,以被调用,他甚至于根本不需要实现Runnable,因为这样往往混淆视听了,为什么呢?也是本文要说的重点。
  在说到timer的原理时,我们先看看Timer里面的一些常见方法:

1、这个方法是调度一个task,经过delay(ms)后开始进行调度,仅仅调度一次。

 public void schedule(TimerTask task, long delay)

2、在指定的时间点time上调度一次。

public void schedule(TimerTask task, Date time)

3、这个方法是调度一个task,在delay(ms)后开始调度,每次调度完后,最少等待period(ms)后才开始调度。

public void schedule(TimerTask task, long delay, long period)

4、和上一个方法类似,唯一的区别就是传入的第二个参数为第一次调度的时间。

public void schedule(TimerTask task, Date firstTime, long period)

5、调度一个task,在delay(ms)后开始调度,然后每经过period(ms)再次调度,貌似和方法:schedule是一样的,其实不然,后面你会根据源码看到,schedule在计算下一次执行的时间的时候,是通过当前时间(在任务执行前得到) + 时间片,而scheduleAtFixedRate方法是通过当前需要执行的时间(也就是计算出现在应该执行的时间)+ 时间片,前者是运行的实际时间,而后者是理论时间点,例如:schedule时间片是5s,那么理论上会在5、10、15、20这些时间片被调度,但是如果由于某些CPU征用导致未被调度,假如等到第8s才被第一次调度,那么schedule方法计算出来的下一次时间应该是第13s而不是第10s,这样有可能下次就越到20s后而被少调度一次或多次,而scheduleAtFixedRate方法就是每次理论计算出下一次需要调度的时间用以排序,若第8s被调度,那么计算出应该是第10s,所以它距离当前时间是2s,那么再调度队列排序中,会被优先调度,那么就尽量减少漏掉调度的情况。

public void scheduleAtFixedRate(TimerTask task, long delay, long period)

6、方法同上,唯一的区别就是第一次调度时间设置为一个Date时间,而不是当前时间的一个时间片,我们在源码中会详细说明这些内容。

public void scheduleAtFixedRate(TimerTask task, Date firstTime,long period)

首先看Timer的构造方法有几种:
构造方法1:无参构造方法,简单通过Tiemer为前缀构造一个线程名称:

public Timer() {
    this("Timer-" + serialNumber());
}

创建的线程不为主线程,则主线程结束后,timer自动结束,而无需使用cancel来完成对timer的结束。

构造方法2:传入了是否为后台线程,后台线程当且仅当进程结束时,自动注销掉。

public Timer(boolean isDaemon) {
    this("Timer-" + serialNumber(), isDaemon);
}

 另外两个构造方法负责传入名称和将timer启动:

public Timer(String name, boolean isDaemon) {
      thread.setName(name);
      thread.setDaemon(isDaemon);
      thread.start();
  }

这里有一个thread,这个thread很明显是一个线程,被包装在了Timer类中,我们看下这个thread的定义是:

private TimerThread thread = new TimerThread(queue);

  而定义TimerThread部分的是:
看到这里知道了,Timer内部包装了一个线程,用来做独立于外部线程的调度,而TimerThread是一个default类型的,默认情况下是引用不到的,是被Timer自己所使用的。

  接下来看下有那些属性
  除了上面提到的thread,还有一个很重要的属性是:

private TaskQueue queue = new TaskQueue();

 看名字就知道是一个队列,队列里面可以先猜猜看是什么,那么大概应该是我要调度的任务吧,先记录下了,接下来继续向下看:

  里面还有一个属性是:threadReaper, 它是Object类型,只是重写了finalize方法而已,是为了垃圾回收的时候,将相应的信息回收掉,做GC的回补,也就是当timer线程由于某种 原因死掉了,而未被cancel,里面的队列中的信息需要清空掉,不过我们通常是不会考虑这个方法的,所以知道java写这个方法是干什么的就行了。

  接下来看调度方法的实现:
  对于上面6个调度方法,我们不做一一列举,为什么等下你就知道了:
  来看下方法:

public void schedule(TimerTask task, long delay)

的源码如下:

public void schedule(TimerTask task, long delay) {
       if (delay < 0)
           throw new IllegalArgumentException("Negative delay.");
       sched(task, System.currentTimeMillis()+delay, 0);

这里调用了另一个方法,将task传入,第一个参数传入System.currentTimeMillis()+delay可见为第一次需要执行的时间的 时间点了(如果传入Date,就是对象.getTime()即可,所以传入Date的几个方法就不用多说了),而第三个参数传入了0,这里可以猜下要么是 时间片,要么是次数啥的,不过等会就知道是什么了;另外关于方法:sched的内容我们不着急去看他,先看下重载的方法中是如何做的

  再看看方法:

public void schedule(TimerTask task, long delay,long period)

源码为:

public void schedule(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, System.currentTimeMillis()+delay, -period);
    }

 看来也调用了方法sched来完成调度,和上面的方法唯一的调度时候的区别是增加了传入的period,而第一个传入的是0,所以确定这个参数为时间片, 而不是次数,注意这个里的period加了一个负数,也就是取反,也就是我们开始传入1000,在调用sched的时候会变成-1000,其实最终阅读完 源码后你会发现这个算是老外对于一种数字的理解,而并非有什么特殊的意义,所以阅读源码的时候也有这些困难所在。

  最后再看个方法是:

public void scheduleAtFixedRate(TimerTasktask,long delay,long period)

源码为:

public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
       if (delay < 0)
           throw new IllegalArgumentException("Negative delay.");
       if (period <= 0)
           throw new IllegalArgumentException("Non-positive period.");
       sched(task, System.currentTimeMillis()+delay, period);
   }

唯一的区别就是在period没有取反,其实你最终阅读完源码,上面的取反没有什么特殊的意义,老外不想增加一个参数来表示 scheduleAtFixedRate,而scheduleAtFixedRate和schedule的大部分逻辑代码一致,因此用了参数的范围来作为 区分方法,也就是当你传入的参数不是正数的时候,你调用schedule方法正好是得到scheduleAtFixedRate的功能,而调用 scheduleAtFixedRate方法的时候得到的正好是schedule方法的功能,呵呵,这些讨论没什么意义,讨论实质和重点:

  来看sched方法的实现体:

private void sched(TimerTask task, long time, long period) {
        if (time < 0)
            throw new IllegalArgumentException("Illegal execution time.");

        synchronized(queue) {
            if (!thread.newTasksMayBeScheduled)
                throw new IllegalStateException("Timer already cancelled.");

            synchronized(task.lock) {
                if (task.state != TimerTask.VIRGIN)
                    throw new IllegalStateException(
                        "Task already scheduled or cancelled");
                task.nextExecutionTime = time;
                task.period = period;
                task.state = TimerTask.SCHEDULED;
            }

            queue.add(task);
            if (queue.getMin() == task)
                queue.notify();
        }
    }

queue为一个队列,我们先不看他数据结构,看到他在做这个操作的时候,发生了同步,所以在timer级别,这个是线程安全的,最后将task相关的参数赋值,主要包含nextExecutionTime(下一次执行时间),period(时间片),state(状态),然后将它放入queue队列中,做一次notify操作,为什么要做notify操作呢?看了后面的代码你就知道了。

  简言之,这里就是讲task放入队列queue的过程,此时,你可能对queue的结构有些兴趣,那么我们先来看看queue属性的结构TaskQueue:

class TaskQueue {

    private TimerTask[] queue = new TimerTask[128];

    private int size = 0;

可见,TaskQueue的结构很简单,为一个数组,加一个size,有点像ArrayList,是不是长度就128呢,当然不 是,ArrayList可以扩容,它可以,只是会造成内存拷贝而已,所以一个Timer来讲,只要内部的task个数不超过128是不会造成扩容的;内部 提供了add(TimerTask)、size()、getMin()、get(int)、removeMin()、quickRemove(int)、 rescheduleMin(long newTime)、isEmpty()、clear()、fixUp()、fixDown()、heapify();

实践部分:

1、通过继承TimerTask的方式实现
  必须重写run方法.

public class MyTask extends TimerTask
{

    @Override
    public void run()
    {
        SimpleDateFormat sdf = null;
        sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        System.out.println("当前时间:" + sdf.format(new Date()));

    }

}

{
    public static void main(String[] args)
    {
        Timer t = new Timer(); // 建立Timer对象
        MyTask task = new MyTask(); //定义任务
        t.schedule(task, 1000,2000);//设置任务的执行,1秒后开始,每2秒执行一次

        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.MINUTE, 30);

        t.schedule(task, cal.getTime() , 2000);


    }
}

2、通过匿名内部类实现

Timer timer = new Timer();  
        timer.scheduleAtFixedRate(new TimerTask() {  
                public void run() {  

                    System.out.println("abc");  
                }  
        }, 1000 , 1000);

  最近需要用到定时调用的功能。可以通过java的Timer类来进行定时调用,下面是有关Timer的一些相关知识。

  其实就Timer来讲就是一个调度器,而TimerTask呢只是一个实现了run方法的一个类,而具体的TimerTask需要由你自己来实现,例如这样:

Timer timer = new Timer();
timer.schedule(new TimerTask() {
        public void run() {
            System.out.println("11232");
        }
}, 200000 , 1000);

  这里直接实现一个TimerTask(当然,你可以实现多个TimerTask,多个TimerTask可以被一个Timer会被分配到多个 Timer中被调度,后面会说到Timer的实现机制就是说内部的调度机制),然后编写run方法,20s后开始执行,每秒执行一次,当然你通过一个 timer对象来操作多个timerTask,其实timerTask本身没什么意义,只是和timer集合操作的一个对象,实现它就必然有对应的run 方法,以被调用,他甚至于根本不需要实现Runnable,因为这样往往混淆视听了,为什么呢?也是本文要说的重点。
在说到timer的原理时,我们先看看Timer里面的一些常见方法:

1、这个方法是调度一个task,经过delay(ms)后开始进行调度,仅仅调度一次。

    public void schedule(TimerTask task, long delay)

2、在指定的时间点time上调度一次。

public void schedule(TimerTask task, Date time)

3、这个方法是调度一个task,在delay(ms)后开始调度,每次调度完后,最少等待period(ms)后才开始调度。

public void schedule(TimerTask task, long delay, long period)

4、和上一个方法类似,唯一的区别就是传入的第二个参数为第一次调度的时间。

public void schedule(TimerTask task, Date firstTime, long period)

5、调度一个task,在delay(ms)后开始调度,然后每经过period(ms)再次调度,貌似和方法:schedule是一样的,其实不然,后面你会根据源码看到,schedule在计算下一次执行的时间的时候,是通过当前时间(在任务执行前得到) + 时间片,而scheduleAtFixedRate方法是通过当前需要执行的时间(也就是计算出现在应该执行的时间)+ 时间片,前者是运行的实际时间,而后者是理论时间点,例如:schedule时间片是5s,那么理论上会在5、10、15、20这些时间片被调度,但是如果由于某些CPU征用导致未被调度,假如等到第8s才被第一次调度,那么schedule方法计算出来的下一次时间应该是第13s而不是第10s,这样有可能下次就越到20s后而被少调度一次或多次,而scheduleAtFixedRate方法就是每次理论计算出下一次需要调度的时间用以排序,若第8s被调度,那么计算出应该是第10s,所以它距离当前时间是2s,那么再调度队列排序中,会被优先调度,那么就尽量减少漏掉调度的情况。

public void scheduleAtFixedRate(TimerTask task, long delay, long period)

6、方法同上,唯一的区别就是第一次调度时间设置为一个Date时间,而不是当前时间的一个时间片,我们在源码中会详细说明这些内容。

public void scheduleAtFixedRate(TimerTask task, Date firstTime,long period)

首先看Timer的构造方法有几种:
构造方法1:无参构造方法,简单通过Tiemer为前缀构造一个线程名称:

public Timer() {
    this("Timer-" + serialNumber());
}

创建的线程不为主线程,则主线程结束后,timer自动结束,而无需使用cancel来完成对timer的结束。

构造方法2:传入了是否为后台线程,后台线程当且仅当进程结束时,自动注销掉。

public Timer(boolean isDaemon) {
    this("Timer-" + serialNumber(), isDaemon);
}

另外两个构造方法负责传入名称和将timer启动:

public Timer(String name, boolean isDaemon) {
      thread.setName(name);
      thread.setDaemon(isDaemon);
      thread.start();
  }

这里有一个thread,这个thread很明显是一个线程,被包装在了Timer类中,我们看下这个thread的定义是:

private TimerThread thread = new TimerThread(queue);

  而定义TimerThread部分的是:

看到这里知道了,Timer内部包装了一个线程,用来做独立于外部线程的调度,而TimerThread是一个default类型的,默认情况下是引用不到的,是被Timer自己所使用的。

  接下来看下有那些属性
  除了上面提到的thread,还有一个很重要的属性是:

private TaskQueue queue = new TaskQueue();

 看名字就知道是一个队列,队列里面可以先猜猜看是什么,那么大概应该是我要调度的任务吧,先记录下了,接下来继续向下看:

  里面还有一个属性是:threadReaper, 它是Object类型,只是重写了finalize方法而已,是为了垃圾回收的时候,将相应的信息回收掉,做GC的回补,也就是当timer线程由于某种 原因死掉了,而未被cancel,里面的队列中的信息需要清空掉,不过我们通常是不会考虑这个方法的,所以知道java写这个方法是干什么的就行了。

  接下来看调度方法的实现:
  对于上面6个调度方法,我们不做一一列举,为什么等下你就知道了:
  来看下方法:

public void schedule(TimerTask task, long delay)

的源码如下:

public void schedule(TimerTask task, long delay) {
       if (delay < 0)
           throw new IllegalArgumentException("Negative delay.");
       sched(task, System.currentTimeMillis()+delay, 0);
}

这里调用了另一个方法,将task传入,第一个参数传入System.currentTimeMillis()+delay可见为第一次需要执行的时间的 时间点了(如果传入Date,就是对象.getTime()即可,所以传入Date的几个方法就不用多说了),而第三个参数传入了0,这里可以猜下要么是 时间片,要么是次数啥的,不过等会就知道是什么了;另外关于方法:sched的内容我们不着急去看他,先看下重载的方法中是如何做的

  再看看方法:

public void schedule(TimerTask task, long delay,long period)

 源码为:

public void schedule(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, System.currentTimeMillis()+delay, -period);
    }

看来也调用了方法sched来完成调度,和上面的方法唯一的调度时候的区别是增加了传入的period,而第一个传入的是0,所以确定这个参数为时间片, 而不是次数,注意这个里的period加了一个负数,也就是取反,也就是我们开始传入1000,在调用sched的时候会变成-1000,其实最终阅读完 源码后你会发现这个算是老外对于一种数字的理解,而并非有什么特殊的意义,所以阅读源码的时候也有这些困难所在。

最后再看个方法是:

public void scheduleAtFixedRate(TimerTasktask,long delay,long period)

源码为:

public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
       if (delay < 0)
           throw new IllegalArgumentException("Negative delay.");
       if (period <= 0)
           throw new IllegalArgumentException("Non-positive period.");
       sched(task, System.currentTimeMillis()+delay, period);
   }

唯一的区别就是在period没有取反,其实你最终阅读完源码,上面的取反没有什么特殊的意义,老外不想增加一个参数来表示 scheduleAtFixedRate,而scheduleAtFixedRate和schedule的大部分逻辑代码一致,因此用了参数的范围来作为 区分方法,也就是当你传入的参数不是正数的时候,你调用schedule方法正好是得到scheduleAtFixedRate的功能,而调用 scheduleAtFixedRate方法的时候得到的正好是schedule方法的功能,呵呵,这些讨论没什么意义,讨论实质和重点:

  来看sched方法的实现体:

private void sched(TimerTask task, long time, long period) {
        if (time < 0)
            throw new IllegalArgumentException("Illegal execution time.");

        synchronized(queue) {
            if (!thread.newTasksMayBeScheduled)
                throw new IllegalStateException("Timer already cancelled.");

            synchronized(task.lock) {
                if (task.state != TimerTask.VIRGIN)
                    throw new IllegalStateException(
                        "Task already scheduled or cancelled");
                task.nextExecutionTime = time;
                task.period = period;
                task.state = TimerTask.SCHEDULED;
            }

            queue.add(task);
            if (queue.getMin() == task)
                queue.notify();
        }
    }

queue为一个队列,我们先不看他数据结构,看到他在做这个操作的时候,发生了同步,所以在timer级别,这个是线程安全的,最后将task相关的参数赋值,主要包含nextExecutionTime(下一次执行时间),period(时间片),state(状态),然后将它放入queue队列中,做一次notify操作,为什么要做notify操作呢?看了后面的代码你就知道了。

  简言之,这里就是讲task放入队列queue的过程,此时,你可能对queue的结构有些兴趣,那么我们先来看看queue属性的结构TaskQueue:

class TaskQueue {

    private TimerTask[] queue = new TimerTask[128];

    private int size = 0;

可见,TaskQueue的结构很简单,为一个数组,加一个size,有点像ArrayList,是不是长度就128呢,当然不 是,ArrayList可以扩容,它可以,只是会造成内存拷贝而已,所以一个Timer来讲,只要内部的task个数不超过128是不会造成扩容的;内部 提供了add(TimerTask)、size()、getMin()、get(int)、removeMin()、quickRemove(int)、 rescheduleMin(long newTime)、isEmpty()、clear()、fixUp()、fixDown()、heapify();

实践部分:

1、通过继承TimerTask的方式实现
  必须重写run方法.

public class MyTask extends TimerTask
{

    @Override
    public void run()
    {
        SimpleDateFormat sdf = null;
        sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        System.out.println("当前时间:" + sdf.format(new Date()));

    }

}


public class TestTask
{
    public static void main(String[] args)
    {
        Timer t = new Timer(); // 建立Timer对象
        MyTask task = new MyTask(); //定义任务
        t.schedule(task, 1000,2000);//设置任务的执行,1秒后开始,每2秒执行一次

        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.MINUTE, 30);

        t.schedule(task, cal.getTime() , 2000);


    }
}

2、通过匿名内部类实现

Timer timer = new Timer();  
        timer.scheduleAtFixedRate(new TimerTask() {  
                public void run() {  

                    System.out.println("abc");  
                }  
        }, 1000 , 1000);

转载来源:https://www.cnblogs.com/0201zcr/p/4703061.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

java Timer(定时调用、实现固定时间执行) 的相关文章

随机推荐

  • vue中window.addEventListener(‘scroll‘, xx)失效解决办法

    多次尝试都无法获取到滚动事件 后来加上true之后就可以了 window addEventListener scroll this clintHeight true
  • js中startsWith()使用

    startsWith函数 时Java中的 在js使用时他并不是每个浏览器都有的 所以我们一般要重写一下这个函数 采用正则表达式实现startWith endWith效果函数 String prototype startWith functi
  • CPU 风扇清理灰尘加油全过程图解

    主机电源风扇由于使用时间长 风扇轴承的润滑油耗尽 导致风扇转速下降或是不转 引起电源热量无法有效排除而造成电脑经常死机 解决办法有几种 现图解说明最简单省钱的办法如下 1 把电源从主机上拆下 如下图再取出电源背面的4个固定镙丝 2008 5
  • EasyAR4.0使用说明(Unity3D)(七)----稀疏空间地图

    稀疏空间地图的对应用环境的要求和平面图像识别可以比照理解 周围环境需要足够丰富 不能有大片的单色区域 透明区域 此外 光照 角度都会对建立地图和定位产生影响 官方给出了建立地图和定位地图的建议 https help easyar cn Ea
  • rgss加密文件解包器_Galgame汉化中的逆向 (一):文本加密(压缩)与解密

    本文为看雪论坛优秀文章 看雪论坛作者ID devseed 0x0 前言 看到关于游戏汉化相关的逆向教程挺少的 作为某汉化组的成员也帮过别的汉化组 于是就想把我见到的几个典型的例子整理分析一下 还是挺有意思的 此教程和我在贴吧和隔壁发的一样
  • 时序预测

    时序预测 Python实现NARX DNN空气质量预测 目录 时序预测 Python实现NARX DNN空气质量预测 效果一览 基本介绍 研究内容 程序设计 参考资料 效果一览 基本介绍 时序预测 Python实现NARX DNN空气质量预
  • 如何使a==1&&a==2&&a==3表达式成立?

    前几天闲着无聊 玩手机无意中发现一个题 觉得挺有意思的 就顺手记录一下 题目 a 1 a 2 a 3 true 思考 我思考了一会 这让一个值既是1又是2又是3的 不可能吧 这肯定是一个伪命题 但突然我灵光一现 对象属性不是可以拦截吗 我能
  • Flex 构建路径

    然libs文件夹是构建路径的一部分 但它并不总是SWC的理想存放位置 当多个项目同时使用相同的SWC时 就不能都存放在libs文件夹中 在这种情况下 SWC可以保持在中心位置 众所周知 SWC路径可以被添加到构建路径中 虽然这意味着需要建立
  • 问题 G: 用递归的方法求值

    题目描述 求1 2 3 4 5 n的值 输入格式 一个n n不大于10000 输出格式 输出1到n的累加和 输入样例 复制 2 输出样例 复制 3 这道题比较简单 边界是n 0 核心代码为 if n 0 return 0 else retu
  • C++ template 模板的模板参数(5.4节)

    有时 让模板参数本身成为模板是很有用的 我们将继续以stack类模板作为例子 来说明模板的模板参数的用途 在Stack的例子中 如果要使用一个和缺省值不同的内部容器 程序员必须两次指定元素类型 也就是说 为了指定内部容器的类型 你需要同时传
  • java实现文件的上传和下载

    文件的上传 upload 文件上传 客户端通过表单的文件域file 把客户端的文件 上传保存到服务器的硬盘上 页面 首先对上传的表单有以下要求 必须有文件域 input type file 表单提交方式 method post 表单的 en
  • 【剑指Offer】35.复杂链表的复制(JS实现)

    题目描述 请实现 copyRandomList 函数 复制一个复杂链表 在复杂链表中 每个节点除了有一个 next 指针指向下一个节点 还有一个 random 指针指向链表中的任意节点或者 null 示例1 输入 head 7 null 1
  • 图灵机模拟程序功能设计

    图灵机由无限长的纸带 读写头 状态寄存器 控制规则等四部分组成 纸带上的符号可以是 0 1 空格 要利用图灵机求解一个问题 需要自己设计图灵机 程序 即定义一些状态 其中包括初始状态和结束状态 设计给出控制规则 并进行图灵机初始化 设定初始
  • chrome/Edge搜索技巧

    1 剔除干扰项搜索 搜索内容 不想要的关键词 排除干扰项 2 特定搜索 给关键词加引号 关键词 只搜索引号里面的字 3 指定网站内搜索 site 域名 关键词 4 指定格式搜索 filetype 文件格式 关键词 可以制定pdf doc p
  • series not exists. Legend data should be same with series name or data name.

    normal删除
  • php+vscode+xdebug搭建php调试环境

    php vscode xdebug搭建php调试环境 开发环境 windows 10 php 8 0 23 xdebug 3 1 6 配置xdebug 查看php版本信息 cmd exe php version 可看到 我的版本信息为8 0
  • 阿里云maven 仓库地址配置

    参考 https help aliyun com document detail 102512 html spm a2c40 aliyun maven repo 0 0 36183054erKD4V 配置指南 maven配置指南 打开mav
  • 深度学习之生成对抗网络(7)WGAN原理

    深度学习之生成对抗网络 7 WGAN原理 1 JS散度的缺陷 2 EM距离 3 WGAN GP WGAN算法从理论层面分析了GAN训练不稳定的原因 并提出了有效的解决方法 那么是什么原因导致了GAN训练如此不稳定呢 WGAN提出是因为JS散
  • Best Cow Fences (前缀和 + 二分)

    描述 Farmer John s farm consists of a long row of N 1 lt N lt 100 000 fields Each field contains a certain number of cows
  • java Timer(定时调用、实现固定时间执行)

    最近需要用到定时调用的功能 可以通过java的Timer类来进行定时调用 下面是有关Timer的一些相关知识 其实就Timer来讲就是一个调度器 而TimerTask呢只是一个实现了run方法的一个类 而具体的TimerTask需要由你自己