ScheduledThreadPoolExecutor源码跟踪分析

58 篇文章 0 订阅

JDK版本1.6

目录

Demo代码示例

ScheduledThreadPoolExecutor源码分析

PriorityQueue

 offer

poll

DelayQueue

take

 poll

offer

ScheduledThreadPoolExecutor

ScheduledFutureTask

Demo代码示例

 示例了三种调度定时任务的api

public class Main{
	static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2);
	static SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 日期格式
	
	public static void main(String[] args) throws Exception {
		System.out.println("===当前时间" + df.format(new Date()));
		
		Runnable runnable = new Runnable(){
			@Override
			public void run() {
				System.out.println("当前时间" + df.format(new Date()));
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {}
			}
		};
		// 初始延迟1s后,开始周期性的执行任务,上一次任务执行结束和下此任务执行开始,间隔2s
		//executor.scheduleWithFixedDelay(runnable, 1, 2, TimeUnit.SECONDS);
		
		// 初始延迟1s后,每两秒执行一次任务,若任务执行时间超过间隔时间,则上次任务执行完毕立即执行下次任务
		executor.scheduleAtFixedRate(runnable, 1, 2, TimeUnit.SECONDS);
		
		// 延迟2s后执行一次任务
		//executor.schedule(runnable, 2, TimeUnit.SECONDS);
		
		System.in.read();
	}
}

ScheduledThreadPoolExecutor源码分析

继承自线程池ThreadPoolExecutor,使用DelayedWorkQueue队列存放任务

 而DelayedWorkQueue内部则由延时队列DelayQueue实现

 DelayQueue又依赖优先级队列PriorityQueue,所以从最内部开始分析

PriorityQueue

优先级队列,使用最小堆来进行优先级排序,内部数组来存放数据,默认初始大小11,因为要使用最小堆,所以要求元素必须能够比较,要么元素实现Comparable接口,要么构造方法中传入比较器Comparator

public class PriorityQueue<E> extends AbstractQueue<E>
    implements java.io.Serializable {
    private static final int DEFAULT_INITIAL_CAPACITY = 11;  

    private transient Object[] queue;

    private int size = 0;

    private final Comparator<? super E> comparator;
    
    public PriorityQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }
   public PriorityQueue(int initialCapacity) {
        this(initialCapacity, null);
    }
   public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }
}

 offer

每次添加元素,都根据元素当前大小,放置合适位置,始终保持数组头元素最小

   public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        // 扩容
        if (i >= queue.length)
            grow(i + 1);
        size = i + 1;
        
        // 如果不是添加第一个元素,把元素e放置在数组最后,重新向上调整堆
        if (i == 0)
            queue[0] = e;
        else
            siftUp(i, e);
        return true;
    }

    // 向上调整,判断是使用元素本身实现的Comparable,还是传入的比较器Comparator
    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            siftUpComparable(k, x);
    }
    // 只看一个,另一个一样的,是个元素x向上寻找位置的过程
    private void siftUpComparable(int k, E x) {
        // 强转类型Comparable,添加的元素没有实现Comparable报错castException
        Comparable<? super E> key = (Comparable<? super E>) x;
        while (k > 0) {
            // 减1右移,是父节点的下标
            int parent = (k - 1) >>> 1;
           
            // 父节点对象
            Object e = queue[parent];

            // 本次添加的对象x比父节点大,则不用继续往上调整了,跳出循环,当前k就是元素x的位置
            if (key.compareTo((E) e) >= 0)
                break;
            // 否则,互换位置,把父节点对象放到k位置,x暂定放到父节点位置,再次向上寻找
            queue[k] = e;
            k = parent;
        }
        queue[k] = key;
    }

poll

每次取出元素,都重新调整数组,始终保持数组头元素最小

    public E poll() {
        if (size == 0)
            return null;
        int s = --size;
        modCount++;

        // 数组第一个即优先级最高的,最小堆堆顶最小的元素
        E result = (E) queue[0];
        // 取出数组最后一个元素,暂定放置到堆顶
        E x = (E) queue[s];
        queue[s] = null;
        // 开始从堆顶向下调整,调整为最小堆
        if (s != 0)
            siftDown(0, x);
        return result;
    }
    
   private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }

    private void siftDownComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>)x;

        // 下标过半以后,就不需要向下调整了,因为没有子节点。可以自己画个堆图看看
        int half = size >>> 1;        // loop while a non-leaf
        while (k < half) {
            // 左移加1,为两个子节点的左侧下标
            int child = (k << 1) + 1; // assume left child is least
            Object c = queue[child];
            int right = child + 1;
            // 左侧和右侧子节点比较,取较小的赋值给c
            if (right < size &&
                ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
                c = queue[child = right];

            // 和子节点中较小的那个比较,若父节点较小,则当前k为元素x的最终位置
            if (key.compareTo((E) c) <= 0)
                break;
            // 否则交换,子节点上位,元素x暂定child位置,继续向下比较调整
            queue[k] = c;
            k = child;
        }
        queue[k] = key;
    }

DelayQueue

延时队列,添加的元素都必须继承Delayed接口,接口只有一个方法getDelay(),返回元素当前剩余的时间,时间小于等于0才能被poll或take取出

take

阻塞获取到点了的元素

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            // take为阻塞方法,直到获取到一个元素
            for (;;) {
                // 查看优先级队列堆头元素,即最小的元素
                E first = q.peek();
                if (first == null) {
                    // 不存在元素,阻塞
                    available.await();
                } else {
                    // 存在元素,获取元素的延时剩余时间
                    long delay =  first.getDelay(TimeUnit.NANOSECONDS);
                    if (delay > 0) {
                        // 还没到时候,阻塞一会
                        long tl = available.awaitNanos(delay);
                    } else {
                        // 到时间了,取出元素返回,并唤醒其他阻塞中的线程
                        E x = q.poll();
                        assert x != null;
                        if (q.size() != 0)
                            available.signalAll(); // wake up other takers
                        return x;
                    }
                }
            }
        } finally {
            lock.unlock();
        }
    }

 poll

与take区别为 只是尝试获取,获取不到返回null

    public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            E first = q.peek();
            if (first == null || first.getDelay(TimeUnit.NANOSECONDS) > 0)
                return null;
            else {
                E x = q.poll();
                assert x != null;
                if (q.size() != 0)
                    available.signalAll();
                return x;
            }
        } finally {
            lock.unlock();
        }
    }

offer

   public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // peek返回优先级队列栈顶最小元素
            E first = q.peek();
            // 优先级队列添加元素
            q.offer(e);

            // DelayQueue.take()方法为阻塞方法,队头元素没到点,线程会阻塞
            // 判断原最小元素比新元素大,则表示可能马上就到点了,立即唤醒等待着的线程对该元素进行检测
            if (first == null || e.compareTo(first) < 0)
                available.signalAll();
            return true;
        } finally {
            lock.unlock();
        }
    }

ScheduledThreadPoolExecutor

  • schedule(Runnable command, long delay, TimeUnit unit) 延迟指定的delay时长之后,只执行一次任务

  • scheduleAtFixedRate(Runnable command, long initialDelay, long period,TimeUnit unit) 初始延迟1s后,每两秒执行一次任务,若任务执行时间超过间隔时间,则上次任务执行完毕立即执行下次任务
  • scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit) 初始延迟1s后,开始周期性的执行任务,上一次任务执行结束和下此任务执行开始,间隔2s

 

 三个api方法代码区别就是ScheduledFutureTask对象,下面详细看源码,至于delayedExecute(t)如下,如果核心线程数没有到定义的数量,则新启动一个,延时队列加入当前ScheduledFutureTask对象

ScheduledFutureTask

new ScheduledFutureTask<Object>(command,null,triggerTime(initialDelay, unit),
                                            unit.toNanos(period))
 
       ScheduledFutureTask(Runnable r, V result, long ns, long period) {
            super(r, result);
            this.time = ns;
            this.period = period;
            this.sequenceNumber = sequencer.getAndIncrement();
        }

构造方法第一个是任务

第二个是Callable的返回值,当前咱看的任务类型是Runnable,所以未null,

第三个是计划的下次触发任务时间点

第四个延迟间隔时间,0表示只执行一次schedule,大于0表示scheduleAtFixedRate,小于0表示scheduleWithFixedDelay

triggerTime()

获取下此应该触发的时间

    private long triggerTime(long delay, TimeUnit unit) {
         return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));
    }

    long triggerTime(long delay) {
         return now() +
             ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
    }
    // 因为后续元素根据延迟时间进行加减比较,判断谁先执行
    // 所以这里判断新元素和延迟队列中最小的元素相差是否超过了long最大值
    // 如果超过,调整新元素的延迟时间
    private long overflowFree(long delay) {
        Delayed head = (Delayed) super.getQueue().peek();
        if (head != null) {
            long headDelay = head.getDelay(TimeUnit.NANOSECONDS);
            if (headDelay < 0 && (delay - headDelay < 0))
                delay = Long.MAX_VALUE + headDelay;
        }
        return delay;
    }

 实现的Delayed接口方法,返回距离触发时间还剩多少ns

 compareTo(),判断优先级,即判断触发时间的远近

 run()

判断周期执行或者只执行一次

      public void run() {
            // 是否是周期任务,即判断构造方法第四个参数是否是0,不是0就是周期任务
            if (isPeriodic())
                runPeriodic();
            else
                // 非周期任务,执行一次了事
                ScheduledFutureTask.super.run();
        }

       // 周期任务,先执行一次,再重置触发时间,再次添加到延迟队列中,等待worker线程再次执行
        private void runPeriodic() {
            boolean ok = ScheduledFutureTask.super.runAndReset();
            boolean down = isShutdown();
            // Reschedule if not cancelled and not shutdown or policy allows
            if (ok && (!down ||
                       (getContinueExistingPeriodicTasksAfterShutdownPolicy() &&
                        !isStopped()))) {
                long p = period;
                if (p > 0)
                    // 从上次的计划触发时间往后推迟p,所以为固定频率,如果执行时间超过了延迟时间,会立即执行,因为会排到堆头,且时间已经到位
                    time += p;
                else
                    // 从当前往后推迟,即本次结束到下此开始间隔p
                    time = triggerTime(-p);
                // 添加队列,等下次执行
                ScheduledThreadPoolExecutor.super.getQueue().add(this);
            }
            // This might have been the final executed delayed
            // task.  Wake up threads to check.
            else if (down)
                interruptIdleWorkers();
        }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值