定时线程池

目录

定时线程池基本介绍

 ScheduledThreadPoolExecutor介绍

ScheduledExecutorService介绍

阻塞队列使用的是DelayedWorkQueue()

常用的定时任务区别 


定时线程池基本介绍

用来处理延迟任务或者定时任务

它接受ScheduledFutureTask类型的任务,是线程调度任务的最小单位,有三种提交任务的方式

  • schedule

  • scheduledAtFixedRate

  • scheduledWithFixedDelay

 ScheduledFutureTask继承结构图

它采用DelayQueue存储等待的任务

  • DelayQueue内部封装了一个PriorityQueue,他会根据time的先后时间顺序,若time相同则根据sequenceNumber排序

  • DelayQueue也是一个无界队列

 ScheduledThreadPoolExecutor介绍

一种ThreadPoolExecutor,可以另外安排命令在给定延迟后运行,或定期执行。当需要多个工作线程时,或者当需要ThreadPoolExecutor(该类扩展)的额外灵活性或功能时,该类比Timer更可取。

延迟任务在启用之前执行,但在启用后何时开始没有任何实时保证。按提交的先进先出(FIFO)顺序启用为完全相同的执行时间安排的任务。

当提交的任务在运行前被取消时,执行将被抑制。默认情况下,此类已取消的任务不会自动从工作队列中删除,直到其延迟消失。虽然这样可以进行进一步的检查和监视,但也可能会导致取消任务的无限保留。要避免这种情况,请将setRemoveOnCancelPolicy设置为true,这将导致在取消任务时立即从工作队列中删除任务。

通过scheduleAtFixedRate或scheduleWithFixedDelay调度的任务的连续执行不会重叠。虽然不同的执行可能由不同的线程执行,但先前执行的效果先于后续执行的效果。

虽然该类继承自ThreadPoolExecutor,但一些继承的优化方法对它没有用处。特别是,因为它使用corePoolSize线程和无界队列充当固定大小的池,所以对maximumPoolSize的调整没有任何有用的效果。此外,将corePoolSize设置为零或使用allowCoreThreadTimeOut几乎从来都不是一个好主意,因为一旦有资格运行任务,这可能会使池中没有线程来处理任务。

扩展说明:此类重写execute和submit方法以生成内部ScheduledFuture对象,以控制每个任务的延迟和调度。为了保留功能,子类中这些方法的任何进一步重写都必须调用超类版本,这将有效地禁用额外的任务自定义。但是,此类提供了替代的受保护扩展方法decorateTask(可运行和可调用各一个版本),可用于自定义用于执行通过execute、submit、scheduleAtFixedRate和scheduleWithFixedDelay输入的命令的具体任务类型。默认情况下,ScheduledThreadPoolExecutor使用扩展FutureTask的任务类型。

ScheduledExecutorService介绍

/*
 * 一种ExecutorService,可以安排命令在给定延迟后运行,或定期执行。
 * schedule方法创建具有各种延迟的任务,并返回可用于取消或检查执行的任务对象。 
 * scheduleAtFixedRate和scheduleWithFixedDelay方法创建并执行定期运行直到取消的任务。
 * 使用执行器提交的命令。execute(Runnable)和ExecutorService提交方法的调度请求延迟为 
 * 零。在调度方法中也允许零延迟和负延迟(但不允许周期),并将其视为立即执行的请求。
 * 所有调度方法都接受相对延迟和周期作为参数,而不是绝对时间或日期。转换表示为java的绝对 
 * 时间是一件简单的事情。util。按要求的格式填写日期。例如,要在未来某个日期进行计划,可 
 * 以使用:schedule(task,date.getTime()-System.currentTimeMillis(), 
 * TimeUnit.millises)。但是,请注意,由于网络时间同步协议、时钟漂移或其他因素,相对延迟 
 * 的到期不必与启用任务的当前日期一致。
 * Executors类为该包中提供的ScheduledExecutorService实现提供了方便的工厂方法。
 */
public interface ScheduledExecutorService extends ExecutorService {

    /**
      * 创建并执行在给定延迟后启用的ScheduledFuture
     * @param command–要执行的任务
     * @param delay–延迟的执行的时间
     * @param unit–delay参数的时间单位 
     * @return 一个ScheduledFuture,表示任务的挂起完成,其{@code get()}方法将在完成时 
     * 返回{@code null}
     * @throws RejectedExecutionException–如果无法安排任务执行
     * @throws NullPointerException–如果命令为null
     */
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);

    /**
      * 创建并执行在给定延迟后启用的ScheduledFuture
     * @param command–要执行的任务
     * @param delay–延迟的执行的时间
     * @param unit–delay参数的时间单位 
     * @return 可用于提取结果或取消的ScheduledFuture
     * @throws RejectedExecutionException–如果无法安排任务执行
     * @throws NullPointerException–如果命令为null
     */
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay, TimeUnit unit);

    /**
     * 创建并执行一个周期性动作,该动作在给定的初始延迟后首先启用,然后在给定的时间段内 
     * 启用;也就是说,执行将在initialDelay之后开始,然后是initialDelay+period,然后是                
     * initialDelay+2*period,依此类推。如果任务的任何执行遇到异常,则会抑制后续执行。否 
     * 则,任务将仅通过取消或终止执行者而终止。如果此任务的任何执行时间超过其周期,则后 
     * 续执行可能会延迟开始,但不会同时执行。 向注册中心发送心跳
     * @param command–要执行的任务
     * @param initialDelay–延迟第一次执行的时间
     * @param period:连续执行之间的时间
     * @param unit–initialDelay和delay参数的时间单位 
     * @return 一个ScheduledFuture,表示任务的挂起完成,其get()方法将在取消时引发异常
     * @throws RejectedExecutionException–如果无法安排任务执行
     * @throws NullPointerException–如果命令为null
     * @throws IllegalArgumentException–如果延迟小于或等于零
     * 如果采用这种发送方式,假设period为2s,中间的逻辑为5s,initialDelay为1s
     * 先延迟1s,执行中间逻辑5s,因为5s>2s,所以第二次的执行和第一次的执行中间不会有停顿时 
     * 间
     */
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);

    /**
     * 创建并执行一个周期性操作,该操作在给定的初始延迟后首先启用,然后在一个执行的终止 
     * 和下一个执行的开始之间具有给定的延迟。如果任务的任何执行遇到异常,则会抑制后续执 
     * 行。否则,任务将仅通过取消或终止执行者而终止。
     * @param command–要执行的任务
     * @param initialDelay–延迟第一次执行的时间
     * @param delay:次执行终止与下一次执行开始之间的延迟  该任务结束完之后在延迟delay秒
     * @param unit–initialDelay和delay参数的时间单位 
     * @return 一个ScheduledFuture,表示任务的挂起完成,其get()方法将在取消时引发异常
     * @throws RejectedExecutionException–如果无法安排任务执行
     * @throws NullPointerException–如果命令为null
     * @throws IllegalArgumentException–如果延迟小于或等于零
     */
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);
}

适用场景:

​   分布式锁-redis(看门狗)

SpringCloud-Eureka的结构图 

 利用scheduleAtFixedRate()方法模拟发送心跳:

//实体类
public class HeartBeat implements Serializable {
    private String ip;
    private String port;
    private String appName;

    public HeartBeat() {
    }

    public HeartBeat(String ip, String port, String appName) {
        this.ip = ip;
        this.port = port;
        this.appName = appName;
    }
    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public String getPort() {
        return port;
    }

    public void setPort(String port) {
        this.port = port;
    }

    public String getAppName() {
        return appName;
    }

    public void setAppName(String appName) {
        this.appName = appName;
    }

}

//服务端代码
@Slf4j
public class EurekaServer {
    public static void main(String[] args) throws IOException {
            ServerSocket serverSocket = new ServerSocket(8888);
            while (true){
                Socket accept = serverSocket.accept();
                new Thread( () -> {
                    try {
                        BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
                        byte[] arr = new byte[1024];
                        int readLength ;
                        StringBuilder sb = new StringBuilder();
                        while ((readLength = bis.read(arr))!= -1){
                            sb.append(new String(arr,0,readLength));
                        }
                        HeartBeat heartBeat = JSON.parseObject(sb.toString(), HeartBeat.class);
                        log.info("heartbeat info =>" + heartBeat);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }).start();
            }
    }
}

//客户端代码
@Slf4j
public class EurekaClient {
    public static void main(String[] args) {
        ScheduledThreadPoolExecutor scheduledThreadPool =
                new ScheduledThreadPoolExecutor(1);
        scheduledThreadPool.scheduleAtFixedRate(() -> {
            Socket socket = null;
            try {
                socket = new Socket("127.0.0.1", 8888);
                BufferedOutputStream dos = new BufferedOutputStream(socket.getOutputStream());
                HeartBeat heartBeat = new HeartBeat();
                heartBeat.setAppName("Order-Service");
                heartBeat.setIp("192.168.124.111");
                heartBeat.setPort("1234");
                String string = JSON.toJSONString(heartBeat);
                log.info("發送的數據"+string);
                dos.write(string.getBytes());
                //使用缓冲流写数据时,一定要执行flush方法
                // (卡在这好久,换了DataoutputStream封装socket.getoutputStream发现可以,才想到这个问题)
                dos.flush();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        },1000,5000, TimeUnit.MILLISECONDS);
    }
}

阻塞队列使用的是DelayedWorkQueue()

public class ScheduledThreadPoolRunner {
    public static void main(String[] args) {
        ScheduledThreadPoolExecutor scheduledThreadPool = new ScheduledThreadPoolExecutor(1);
        scheduledThreadPool.schedule(() ->{
            System.out.println("我要延迟5s");
        },5000,TimeUnit.MILLISECONDS);
        scheduledThreadPool.schedule(() ->{
            System.out.println("我要延迟10s");
        },10000,TimeUnit.MILLISECONDS);
        scheduledThreadPool.schedule(() ->{
            System.out.println("我要延迟3s");
        },3000,TimeUnit.MILLISECONDS);
        scheduledThreadPool.schedule(() ->{
            System.out.println("我要延迟1s");
        },1000,TimeUnit.MILLISECONDS);
     }
}

//打印结果:我要延迟1s,我要延迟3s,我要延迟5s,我要延迟10s
  • 关于打印结果有此疑问>>>>如何进行排序的?? ---
    • 通过堆结构实现的(在数据结构章节中会有相关知识,等补充完,会直接附上链接)  

 DelayedWorkQueue基于基于堆(堆是表现出来的一种逻辑结构&&存储结构是数组)的数据结构,与DelayQueue和PriorityQueue中的数据结构类似,只是每个ScheduledFutureTask也将其索引记录到堆数组中。这消除了在取消时查找任务的需要,大大加快了删除速度(从O(n)下降到O(logn)),并减少了垃圾保留,否则在清除之前等待元素上升到顶部会发生垃圾保留。但是,由于队列也可能包含非ScheduledFutureTasks的可运行的ScheduledFutures,我们不能保证有这样的索引可用,在这种情况下,我们会退回到线性搜索。(我们预计大多数任务不会被修饰,更快的案例将更常见。)所有堆操作都必须记录索引更改——主要是在siftUp和siftDown中。删除后,任务的heapIndex设置为-1。请注意,ScheduledFutureTasks最多只能在队列中出现一次(对于其他类型的任务或工作队列,这不一定是真的),因此它们由heapIndex唯一标识。

它的内部封装了一个PriorityQueue(根据时间去做排序),他会根据time的先后时间排序,若time相同则根据sequenceNumber排序;
DelayQueue也是一个无界队列

体现在静态内部类:DelayedWorkQueue中的offer()方法里面

接收ScheduledFutureTask类型的任务,是线程池调度的最小单位,有三种提交任务的方式

  • schedule

  • scheduleAtFixRate

  • scheduleWithFixRate

 源码分析

scheduledThreadPool.scheduleWithFixedDelay(
   //task
   () -> System.out.println(System.currentTimeMillis() + "send heart beats");
 }, 1000, 2000, TimeUnit.MILLISECONDS);

//进入到scheduleWithFixedDelay方法里面
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit) {
        //判断任务是否为空
        if (command == null || unit == null)
            throw new NullPointerException();
        if (delay <= 0)
            throw new IllegalArgumentException();
        //将参数封装成ScheduledFutureTask类(无返回值)
        ScheduledFutureTask<Void> sft =
            new ScheduledFutureTask<Void>(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          unit.toNanos(-delay));
        //返回test:该方法被protect修饰,做拓展用的方法  t就是sft 
        RunnableScheduledFuture<Void> t = decorateTask(command, sft);
        //ScheduledFutureTask内部类里面的一个属性.t就是sft指向自己的一个属性
        //reExecutePeriodic重新排队的实际任务
        sft.outerTask = t;
        delayedExecute(t);
        return t;
    }


   /**
     * 修改或替换用于执行可运行的任务。此方法可用于重写用于管理内部任务的具体类。默认实 
     * 现只返回给定的任务。
     *
     * @param runnable 提交的runnable 
     * @param task 为执行runnable命令而创建的任务
     * @param <V> 任务结果的类型
     * @return a task that can execute the runnable
     * @since 1.6
     */
    protected <V> RunnableScheduledFuture<V> decorateTask(
        Runnable runnable, RunnableScheduledFuture<V> task) {
        return task;
    }
/** reExecutePeriodic重新排队的实际任务 */
RunnableScheduledFuture<V> outerTask = this;

   /**
     * 延迟或周期性任务的主要执行方法。如果线程池关闭,则拒绝该任务。否则会将任务添加到 
     * 队列中,并在必要时启动一个线程来运行它。
     *(我们不能预先启动线程来运行任务,因为任务(可能)还不应该运行。)
     * 如果在添加任务时关闭了线程池,如果状态和关机后运行参数要求,则取消并删除它。
     *
     * @param task the task
     */
    private void delayedExecute(RunnableScheduledFuture<?> task) {
        //如果关闭了线程池
        if (isShutdown())
            //拒绝该任务
            reject(task);
        else {
            //将任务添加到队列中
            super.getQueue().add(task);
            //如果关闭了线程池
            if (isShutdown() &&
                !canRunInCurrentRunState(task.isPeriodic()) &&
                remove(task))
                task.cancel(false);
            else
                ensurePrestart();
        }
    }


   /**
     * 与prestartCoreThread相同:即使corePoolSize为0,也至少会启动一个线程。
     */
    void ensurePrestart() {
        //得到线程数
        int wc = workerCountOf(ctl.get());
        //如果小于核心线程数
        if (wc < corePoolSize)
            //增加一个worker
            addWorker(null, true);
        //线程数为0,增加一个worker
        else if (wc == 0)
            addWorker(null, false);
    }



//ScheduledFutureTask:run()方法
        public void run() {
            //是否为周期性任务
            boolean periodic = isPeriodic();
            if (!canRunInCurrentRunState(periodic))
                cancel(false);
            else if (!periodic)
                ScheduledFutureTask.super.run();
            else if (ScheduledFutureTask.super.runAndReset()) {
                //设置下一次的执行时间
                setNextRunTime();
                //实际拿到的还是自己原来的任务
                reExecutePeriodic(outerTask);
            }
        }

/**
  * 设置下一次运行定期任务的时间。
  */
private void setNextRunTime() {
            long p = period;
            //大于0:基于scheduleAtFixedRate()方法提交任务
            if (p > 0)
                time += p;
            //小于0:基于scheduleWithFixedDelay()方法提交任务
            else
                time = triggerTime(-p);
        }



long triggerTime(long delay) {
        //当前时间+要延迟的时间
        return now() +
            ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
    }


/**
     * 
     * 除非当前运行状态排除了周期性任务,否则会重新对其进行排队。与delayedExecute的想法 
     * 相同,只是放弃任务而不是拒绝。
     * @param task the task
     */
    void reExecutePeriodic(RunnableScheduledFuture<?> task) {
        //如果给定当前运行状态和关机后运行参数,则返回true。上一个任务是否已经完成
        if (canRunInCurrentRunState(true)) {
            //添加到队列里面
            super.getQueue().add(task);
            //如果不能
            if (!canRunInCurrentRunState(true) && remove(task))
                //撤销
                task.cancel(false);
            else
                //同上面的方法,会增加一个线程执行任务
                ensurePrestart();
        }
    }

 常用的定时任务区别 

  • Timer:单线程,线程挂了,不会在创建线程执行任务
  • 定时线程池:线程挂了,在提交任务,线程池会创建新的线程执行任务
  • xxl-job:定时任务+分布式调度
  • quartz:单机的定时任务,功能强大
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值