当前位置 : 主页 > 编程语言 > 其它开发 >

Java 多线程定时器分析

来源:互联网 收集:自由互联 发布时间:2022-05-30
之前写 Java 定时任务 用到了 ScheduledExecutorService,自己没有思路来实现定时任务的功能,所以十分好奇其底层代码的实现,于是乎就去翻看源码,在这过程中还发现了无处不在的 Doug L

之前写 Java 定时任务 用到了 ScheduledExecutorService,自己没有思路来实现定时任务的功能,所以十分好奇其底层代码的实现,于是乎就去翻看源码,在这过程中还发现了无处不在的 Doug Lea



1. ScheduledExecutorService

ScheduledExecutorService 是 jdk 提供的计划执行服务接口(实现类是个线程池)。这里举例 newScheduledThreadPool 来分析,其是指定核心线程数的计划线程池


1.1 基本使用
public class ExecutorSchedule {
    public static void main(String[] args) {
        
        // 需要计划执行的任务
        Runnable runnable = () -> {
            System.out.println("执行定时任务");
        };

        // 计划的时间参数
        long delay = 0;
        long period = 1000 * 5;
        
        // 计划线程池执行
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
        service.scheduleAtFixedRate(runnable, delay, period, TimeUnit.MILLISECONDS);
}


1.2 构造函数

通过构造函数知道计划执行的关键是参数中的阻塞队列:new DelayedWorkQueue()

//  创建一个计划线程池执行器
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}


// super 最终指向 ThreadPoolExecutor
// 计划线程池调用 ThreadPoolExecutor 方法来构造线程池(阿里规范要手动传参建立线程池)
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}

// 默认的构造线程池方法
public ThreadPoolExecutor(
    int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue,
    ThreadFactory threadFactory,
    RejectedExecutionHandler handler) {
    
    // xxx
}








2. DelayedWorkQueue

DelayedWorkQueue 顾名思义是一个延迟队列,用来存放线程池待执行的任务


2.1 继承关系

在 DelayedWorkQueue 的类声明中知道,它是一个阻塞队列(线程池并发时保持数据一致性,这部分内容以后会介绍)

// AbstractQueue:有队列的基本方法
// BlockingQueue:有阻塞队列的基本方法
class DelayedWorkQueue extends AbstractQueue<Runnable> implements BlockingQueue<Runnable> {}


2.2 代码实现

在 DelayedWorkQueue 的具体代码中发现 DelayedWorkQueue 的内部数据结构是堆,里面存放了 RunnableScheduledFuture 对象。然后我们大胆猜测: RunnableScheduledFuture 就是延迟任务的核心,里面存放了延迟时间,DelayedWorkQueue 按照这个延迟时间来进行内部排序

// 堆特有的方法,上浮下潜
private void siftUp(int k, RunnableScheduledFuture<?> key) {}

private void siftDown(int k, RunnableScheduledFuture<?> key) {}








3. RunnableScheduledFuture

RunnableScheduledFuture 只是一个接口,ScheduledThreadPoolExecutor 线程池的内部有该接口的实现类(ScheduledFutureTask),我们先来看 RunnableScheduledFuture,然后再分析 ScheduledFutureTask 的接口实现


3.1 接口分析
// RunnableFuture 是异步获取结果的内容
public interface RunnableScheduledFuture<V> extends RunnableFuture<V>, ScheduledFuture<V> {}

// Future 是异步获取结果,和 Runnable 对比
public interface ScheduledFuture<V> extends Delayed, Future<V> {}

// 这里就是我们要找核心实现了
// 继承了 Comparable,在堆里面可按定延迟时间来排序对比
public interface Delayed extends Comparable<Delayed> {

    /**
     * 返回与此对象关联的剩余给定时间单位
     * 
     * @param 时间单位
     * @return 剩余延迟时间,0或负值表示时间已经过去
     */

    long getDelay(TimeUnit unit);
}


3.2 ScheduledFutureTask

ScheduledFutureTask 的构造函数入参有 Runnable、result、ns:分别是 延迟任务、Future 返回、延迟时间

// FutureTask 是异步任务
class ScheduledFutureTask<V> extends FutureTask<V> implements RunnableScheduledFuture<V> {
    
    private final long sequenceNumber;

    private long time;

    private final long period;
    
    // 构造方法
    ScheduledFutureTask(Runnable r, V result, long ns) {
        super(r, result);
        this.time = ns;
        this.period = 0;
        this.sequenceNumber = sequencer.getAndIncrement();
    }
    
    // 获取延迟时间(延迟任务的关键)
    public long getDelay(TimeUnit unit) {
        return unit.convert(time - now(), NANOSECONDS);
    }

    // 入队的对比方法
    public int compareTo(Delayed other) {
        if (other == this) // compare zero if same object
            return 0;
        if (other instanceof ScheduledFutureTask) {
            ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
            long diff = time - x.time;
            if (diff < 0)
                return -1;
            else if (diff > 0)
                return 1;
            else if (sequenceNumber < x.sequenceNumber)
                return -1;
            else
                return 1;
        }
        long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
        return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
    }
}


3.3 实现分析

1.1 基本使用:service.scheduleAtFixedRate(runnable, delay, period, TimeUnit.MILLISECONDS) 就将提交的任务与延迟时间封装成 ScheduledFutureTask,

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
    
    // xxx 省略无关代码
    
    // 在执行延时任务的时候,传入 Runnable,延迟参数创建 ScheduledFutureTask 对象
    ScheduledFutureTask<Void> sft = new ScheduledFutureTask<Void>(command, null, triggerTime(initialDelay, unit), unit.toNanos(period));
    
    // 装饰器
    RunnableScheduledFuture<Void> t = decorateTask(command, sft);
    sft.outerTask = t;
    
    // 延迟执行
    delayedExecute(t);
    return t;
}


// 延迟执行具体实现
private void delayedExecute(RunnableScheduledFuture<?> task) {
    
    // 判断线程池是否关闭
    if (isShutdown())
        
        // 拒绝队列执行
        reject(task);
    else {
        
        // 往 workQueue(即 DelayedWorkQueue)里添加 上一步被封装的 ScheduledFutureTask
        // 至此,该延时任务已经提交到线程池等待执行了
        super.getQueue().add(task);
        if (isShutdown() &&
            !canRunInCurrentRunState(task.isPeriodic()) &&
            remove(task))
            task.cancel(false);
        else
            ensurePrestart();
    }
}








4. 总结过程
  • 我们需要延迟执行的任务被封装成 ScheduledFutureTask
  • 然后被添加到 DelayedWorkQueue 中,队列头部是达到延迟时间的任务(内部堆是按延迟时间排序)
  • 线程池循环执行任务时从 DelayedWorkQueue 中获取,即可实现延迟任务的功能








5. 拓展

参考 ScheduledExecutorService 的设计,我们也能写一个简易的延迟队列出来


5.1 Delayed 对象
public class DelayedTask implements Delayed, Runnable {

    // 任务名
    private String name;

    // 开始时间
    private long start = System.currentTimeMillis();

    // 要延迟的时间
    private long delayTime;

    DelayedTask(String name, long delayTime) {
        this.name = name;
        this.delayTime = delayTime;
    }

    // 返回剩余时间
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert((start + delayTime) - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    // 排序方法
    @Override
    public int compareTo(Delayed o) {
        return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
    }

    @Override
    public void run() {
        System.out.println(this.name + " " + "延迟任务执行了");
    }
}


5.2 延迟线程池
public class DelayedThread extends Thread {

    private BlockingQueue<DelayedTask> queue;

    public DelayedThread(BlockingQueue<DelayedTask> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        DelayedTask target = null;

        while (!queue.isEmpty()) {
            try {
                target = queue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            target.run();
        }
    }
}


5.3 执行事例
public class DelayedTest {
    public static void main(String[] args) {

        DelayedTask task1 = new DelayedTask("1", 1000);
        DelayedTask task2 = new DelayedTask("2", 5000);

        DelayQueue<DelayedTask> delayQueue = new DelayQueue();
        delayQueue.add(task1);
        delayQueue.add(task2);

        DelayedThread delayedThread = new DelayedThread(delayQueue);
        delayedThread.start();
    }
}

// 1 延迟任务执行了
// 2 延迟任务执行了



参考

https://blog.csdn.net/dkfajsldfsdfsd/article/details/88966814



上一篇:小程序字节转GBK及UTF8
下一篇:没有了
网友评论