线程池(四)

ScheduledThreadPoolExecutor

具备延迟执行能力的线程池,任务队列使用无界的DelayQueue。从以下几个方法来看窥探ScheduledThreadPoolExecutor。

ScheduledThreadPoolExecutor(int corePoolSize)

构造器直接调用ScheduledThreadPoolExecutor构造器,由于DelayedWorkQueue是无界的,因此ScheduledThreadPoolExecutor只有coreSize个线程数有效,其他参数Integer.MAX_VALUE, 0, NANOSECONDS不生效。

public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)

public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        RunnableScheduledFuture<?> t = decorateTask(command,
            new ScheduledFutureTask<Void>(command, null,
                                          triggerTime(delay, unit)));
        delayedExecute(t);// 执行任务
        return t;
    }
    
private void delayedExecute(RunnableScheduledFuture<?> task) {
        if (isShutdown())// 如果当前线程池关闭状态,拒绝任务
            reject(task);
        else {
            super.getQueue().add(task);// 将任务放入到延迟队列
            if (isShutdown() &&
                !canRunInCurrentRunState(task.isPeriodic()) &&// 判断在当前状态下是否可以执行,如果为false,移除任务,取消执行当前任务
                remove(task))
                task.cancel(false);
            else
                ensurePrestart();// 正常流程走这里
        }
    }

void ensurePrestart() {
        int wc = workerCountOf(ctl.get());
        if (wc < corePoolSize)
            addWorker(null, true);// 如果有核心线程数且wc小于核心线程数数量,将任务交给核心线程执行
        else if (wc == 0)
            addWorker(null, false);// 如果核心线程数为0,新起一个线程从延迟队列中拿到任务,并执行任务
    }
    
// 线程从队列中获取任务时,如果没有满足当前时间周期的任务时,获取任务失败。因此延迟队列DelayQueue控制来时间周期.内部使用数组实现了小顶堆,将最先执行的任务放在前面
public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();// 可响应中断的方式进行加锁等待
        try {
            for (;;) {
                E first = q.peek();
                if (first == null)
                    available.await();//说明堆中没有任务
                else {
                    long delay = first.getDelay(NANOSECONDS);//用下一次执行时间time-now得到结果,如果小于0,说明需要执行任务
                    if (delay <= 0)
                        return q.poll();// 返回任务,从ThreadPoolExecutor的getTask()中返回
                    first = null; // 如果没有到达下一次任务的执行时间
                    if (leader != null)
                        available.await();// 堆中不是最早执行的其他线程在此等待,等待被堆中最先执行的任务线程唤醒
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            available.awaitNanos(delay);// 堆中最先执行的那个任务线程在此等待
                        } finally {
                            if (leader == thisThread)
                                leader = null;// 判断当前唤醒的线程是否是第一个进来的线程,如果是,需要唤醒后继线程,进入for循环,再次等待在available.awaitNanos(delay)这里,等待超时唤醒
                        }
                    }
                }
            }
        } finally {
            if (leader == null && q.peek() != null)
                available.signal();// leader线程唤醒后继线程
            lock.unlock();// 释放锁,在此进入for循环,此时delay<=0成立,执行return q.poll(),从ThreadPoolExecutor的getTask()返回,执行run方法
        }
    }

// 线程最终调用ScheduledFutureTask的run方法
public void run() {
            boolean periodic = isPeriodic();// 是否是周期性任务
            if (!canRunInCurrentRunState(periodic))// 当前线程池状态是否可以执行
                cancel(false);
            else if (!periodic)// 如果不是周期性任务,直接调用run方法,执行一次
                ScheduledFutureTask.super.run();
            else if (ScheduledFutureTask.super.runAndReset()) {//runAndReset()执行run方法,并且恢复属性至原始状态
                setNextRunTime();// 设置下次执行时间
                reExecutePeriodic(outerTask);// 重新将任务放入延迟队列中
            }
        }

// 设置下次执行时间,如果p>0,说明间隔时间从触发时间开始算。如果p<0说明间隔时间从任务结束时间开始算。scheduleAtFixedRate方法的p>0,而scheduleWithFixedDelay的p<0,这就造成两个方法的执行结果不一致
private void setNextRunTime() {
            long p = period;
            if (p > 0)
                time += p;
            else
                time = triggerTime(-p);
        }

思考

核心线程数设置为1有什么风险?
如果同一时间需要执行的任务数大于1个,那么同一时间只会有一个任务在执行,其他的任务不执行。

因此,延迟任务的核心线程数大小需要考虑任务数量和任务执行频率。

new ScheduledThreadPoolExecutor(1);

demo

public void test02() {
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
        executor.scheduleAtFixedRate(()->{
            System.out.println(new Date() + ":1");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 0, 1, TimeUnit.SECONDS);

        executor.scheduleAtFixedRate(()->{
            System.out.println(new Date() + ":2");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 0, 1, TimeUnit.SECONDS);

        executor.scheduleAtFixedRate(()->{
            System.out.println(new Date() + ":3");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 0, 1, TimeUnit.SECONDS);
    }

针对上述Demo解决方案

可以将ScheduledThreadPoolExecutor当作分发线程池,任务最终交给其他线程执行。

xxl-job里也可以看到这个用法,定时从数据库获取下N个任务触发时间,交给其他线程池,其他线程池在for死循环中判断任务是否可执行。

这样的好处:
1. 由于ScheduledThreadPoolExecutor是无界队列,存在风险。交给其他线程池执行可以控制队列长度以及拒绝策略,更加安全
2. 不需要担心ScheduledThreadPoolExecutor的核心线程数不够的问题

public void test03() {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();// 使用自定义线程池
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
        executor.scheduleAtFixedRate(()->{
            cachedThreadPool.execute(()->{
                System.out.println(new Date() + ":1");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            
        }, 0, 1, TimeUnit.SECONDS);

        executor.scheduleAtFixedRate(()->{
            cachedThreadPool.execute(()->{
                System.out.println(new Date() + ":2");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }, 0, 1, TimeUnit.SECONDS);

        executor.scheduleAtFixedRate(()->{
            cachedThreadPool.execute(()->{
                System.out.println(new Date() + ":3");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }, 0, 1, TimeUnit.SECONDS);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
线程池的四种拒绝策略分别是: 1. AbortPolicy(中止策略):当线程池无法接受新任务时,会抛出一个拒绝执行的异常信息(RejectedExecutionException),中止任务的执行,并需要处理该异常。这是线程池的默认拒绝策略。 2. CallerRunsPolicy(调用者运行策略):当线程池无法接受新任务时,会使用调用线程直接执行任务。适用于并发较小、性能要求不高的场景,不允许任务失败。然而,如果任务提交速度过快,可能会导致程序阻塞,造成性能上的损失。 3. DiscardPolicy(直接丢弃策略):当线程池无法接受新任务时,直接丢弃当前任务,没有任何提示信息或处理。适用于对任务丢失无关紧要的场景,但需要注意任务的丢失可能会带来潜在的问题。 4. DiscardOldestPolicy(丢弃最老任务策略):当线程池无法接受新任务时,丢弃阻塞队列中最老的一个任务,并将新任务加入队列。适用于对任务响应时间要求较高的场景,丢弃最老任务可以腾出空间来执行新任务。 根据具体的业务场景和需求,可以选择合适的拒绝策略来处理无法接受新任务的情况。一般情况下,使用线程池时,默认采用的是AbortPolicy中止策略。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [线程池-四种拒绝策略总结](https://blog.csdn.net/alan_liuyue/article/details/120995601)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [Java线程池的四种拒绝策略](https://blog.csdn.net/a904364908/article/details/107489854)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值