并发编程 定时线程池ScheduledThreadPoolExecutor学习总结

ScheduledThreadPoolExecutor

类结构图:
在这里插入图片描述
用来处理延时任务或定时任务,采用DelayQueue存储等待的任务,DelayQueue内部封装了一个PriorityQueue,它会根据time的先后时间排序,若time相同则根据sequenceNumber排序;
DelayQueue是一个无界队列;

三种提交任务的方式:

schedule:此方法提交任务只会执行一次

使用方法:

public class ScheduleThreadPoolTest {

    public static void main(String[] args) {
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);

        scheduledThreadPoolExecutor.schedule(() -> {
            System.out.println("延迟5s执行");
            return 1;
        //表示线程开启五秒后执行任务
        }, 5000, TimeUnit.MILLISECONDS);
        scheduledThreadPoolExecutor.shutdown();
    }
}

scheduledAtFixedRate:此方法可以设置任务执行周期,如果任务执行时间超过了设置的周期时间,会把任务放到DelayQueue中排队,任务执行完成后立即从队列中获取任务继续执行,可以用来实现心跳确认机制。

使用方法:

public class ScheduleThreadPoolTest {

     public static void main(String[] args) {
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
        //每次过5秒,发送一个心跳确认
        scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {
            log.info("线程任务开始");
            long starttime = System.currentTimeMillis(), nowtime = starttime;
            //死循环,当前时间 - 开始时间小于5秒,继续执行
            while ((nowtime - starttime) < 5000) {
                nowtime = System.currentTimeMillis();
            }
            //五秒后任务结束
            log.info("线程任务结束");
//            throw new RuntimeException("unexpected error , stop working");
        }, 1000, 2000, TimeUnit.MILLISECONDS);
    }
}

scheduledWithFixedDelay:此方法在scheduledThreadPoolExecutor基础上做了优化,如果任务执行时间超过了设置的周期时间,任务执行完后不会立即拿执行下一个,会等设置的周期时间到了之后再执行任务。它和scheduledAtFixedRate有个共同点,线程如果抛异常,会丢弃该任务重新等待获取新的任务,我们可以利用此特性catch到可能异常让线程重新获取任务,或者在catch中做一些业务处理。

使用方法:

public class ScheduleThreadPoolTest {

     public static void main(String[] args) {
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
        //每次过5秒,发送一个心跳确认
        scheduledThreadPoolExecutor.scheduleWithFixedDelay(() -> {
            log.info("线程任务开始");
            long starttime = System.currentTimeMillis(), nowtime = starttime;
            //死循环,当前时间 - 开始时间小于5秒,继续执行
            while ((nowtime - starttime) < 5000) {
                nowtime = System.currentTimeMillis();
            }
            //五秒后任务结束
            log.info("线程任务结束");
//            throw new RuntimeException("unexpected error , stop working");
        }, 1000, 2000, TimeUnit.MILLISECONDS);
    }
}

Timer定时类
timer和ScheduledThreadPoolExecutor线程池类似,但timer中的thread是一个成员实行,在线程抛异常时会终止线程,而ScheduledThreadPoolExecutor则会阻塞等待获取其他任务。

使用方法:

public class TimerTest {

    public static void main(String[] args) {
        //定时类
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                log.info("线程任务开始");
                long starttime = System.currentTimeMillis(), nowtime = starttime;
                //死循环,当前时间 - 开始时间小于5秒,继续执行
                while ((nowtime - starttime) < 5000) {
                    nowtime = System.currentTimeMillis();
                }
                //五秒后任务结束
                log.info("线程任务结束");
//                throw new RuntimeException("unexpected error , stop working");
            }
        }, 1000, 2000);
    }
}

DelayedWorkQueue

DelayedWorkQueue是一个基于堆的数据结构,类似于DelayQueue和PriorityQueue。在执行定时任务的时候,每个任务的执行时间都不同,所以DelayedWorkQueue的工作就是按照执行时间的升序来排列。

堆结构介绍:

堆结构是用数组实现的二叉树,数组下标可以表明元素节点的位置,所以省去指针的内存消耗,会根据父节点是否大于左右节点分为最小根堆和最大根堆。
二叉树根节点小于左右节点叫小根堆
二叉树根节点大于左右节点叫大根堆
请添加图片描述

堆的属性:

1、堆都是满二叉树.因为满二叉树会充分利用数组的内存空间
2、最大堆和最小堆的只需要保证根节点最大或最小,左右节点大小关系无所谓。因为取出、新增、删除操作会对堆进行重新排序,把新的最小或者最大值放到堆顶,如果左右节点要顺序排序的话会扫描整个素组,消耗大量时间。

DelayedWorkQueue属性:
//队列初始容量
private static final int INITIAL_CAPACITY = 16;
//用于记录RunableScheduledFuture任务的数组
private RunnableScheduledFuture<?>[] queue = new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
private final ReentrantLock lock = new ReentrantLock();
//当前队列中任务数,即队列深度
private int size = 0;
//leader线程用于等待队列头部任务,
private Thread leader = null;
//当较新的任务在队列的头部可用时,或者新线程可能需要成为leader,通知其他线程等待
private final Condition available = lock.newCondition();

延迟队列是基于数组实现的,初始容量为16;获取延迟队列中头部节点的线程称为leader,说明leader线程是不断变化的,但leader线程在等待,则其他线程也会等待,直到leader线程获取根节点,且从等待线程中产生新的leader线程。

任务排序方法:
循环的根据key节点与它的父节点来判断,如果key节点的执行时间小于父节点,则将两个节点交换,使执行时间靠前的节点排列在队列的前面。

private void siftUp(int k, RunnableScheduledFuture<?> key) {
//找到父节点的索引
while (k > 0) {
  //获取父节点
        int parent = (k - 1) >>> 1;
        RunnableScheduledFuture<?> e = queue[parent];
  //如果key节点的执行时间大于父节点的执行时间,不需要再排序了
        if (key.compareTo(e) >= 0)
            break;
 //如果key.compareTo(e) < 0,说明key节点的执行时间小于父节点的执行时间,需要把父节点移到后面
        queue[k] = e;
        setIndex(e, k);
//设置索引为k
        k = parent;
    }
//key设置为排序后的位置中
    queue[k] = key;
    setIndex(key, k);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值