ScheduledThreadPoolExecutor使用及原理

看到alibaba的nacos注册中心中client端用这个作为心跳任务工具
BeatReactor类中
executorService spring 管理。

      this.executorService = new ScheduledThreadPoolExecutor(threadCount, new ThreadFactory() {
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setDaemon(true);
                thread.setName("com.alibaba.nacos.naming.beat.sender");
                return thread;
            }
        });
threadCount 取值UtilAndComs.DEFAULT_CLIENT_BEAT_THREAD_COUNT
DEFAULT_CLIENT_BEAT_THREAD_COUNT = Runtime.getRuntime().availableProcessors() > 1 ? Runtime.getRuntime().availableProcessors() / 2 : 1;

ThreadFactory定义,线程池中的线程为守护线程。thread.setDaemon(true);
守护线程:
定义:守护线程--也称“服务线程”,在没有用户线程可服务时会自动离开。
优先级:守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。
设置:通过setDaemon(true)来设置线程为“守护线程”;将一个用户线程设置为
守护线程的方式是在 线程对象创建 之前 用线程对象的setDaemon方法。

调用

// 注册时新建任务
this.executorService.schedule(new BeatReactor.BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS); 5秒后执行一次。
static {
      DEFAULT_HEART_BEAT_TIMEOUT = TimeUnit.SECONDS.toMillis(15L); // 心跳超时时间
      DEFAULT_IP_DELETE_TIMEOUT = TimeUnit.SECONDS.toMillis(30L); // 删除注册服务提供者时间
      DEFAULT_HEART_BEAT_INTERVAL = TimeUnit.SECONDS.toMillis(5L); // 心跳调用服务提供者时间
}
beatInfo.getPeriod()为心跳时间5秒

ScheduledThreadPoolExecutor其底层数据结果是小根堆结构。
堆结果是完全二叉树,分为大根堆和小根堆,大根堆,父节点大于两个子节点。小根堆,父节点小于两个子节点。
因此每次循环都是最先到时间的任务。

new BeatReactor.BeatTask(beatInfo) 每次执行完就重新新建任务

class BeatTask implements Runnable {
        BeatInfo beatInfo;

        public BeatTask(BeatInfo beatInfo) {
            this.beatInfo = beatInfo;
        }

        public void run() {
            if (!this.beatInfo.isStopped()) {
                long nextTime = this.beatInfo.getPeriod();

                try {
                    JsonNode result = BeatReactor.this.serverProxy.sendBeat(this.beatInfo, BeatReactor.this.lightBeatEnabled);
                    long interval = result.get("clientBeatInterval").asLong();
                    boolean lightBeatEnabled = false;
                    if (result.has("lightBeatEnabled")) {
                        lightBeatEnabled = result.get("lightBeatEnabled").asBoolean();
                    }

                    BeatReactor.this.lightBeatEnabled = lightBeatEnabled;
                    if (interval > 0L) {
                        nextTime = interval;
                    }

                    int code = 10200;
                    if (result.has("code")) {
                        code = result.get("code").asInt();
                    }

                    if (code == 20404) {
                        Instance instance = new Instance();
                        instance.setPort(this.beatInfo.getPort());
                        instance.setIp(this.beatInfo.getIp());
                        instance.setWeight(this.beatInfo.getWeight());
                        instance.setMetadata(this.beatInfo.getMetadata());
                        instance.setClusterName(this.beatInfo.getCluster());
                        instance.setServiceName(this.beatInfo.getServiceName());
                        instance.setInstanceId(instance.getInstanceId());
                        instance.setEphemeral(true);

                        try {
                            BeatReactor.this.serverProxy.registerService(this.beatInfo.getServiceName(), NamingUtils.getGroupName(this.beatInfo.getServiceName()), instance);
                        } catch (Exception var10) {
                        }
                    }
                } catch (NacosException var11) {
                    LogUtils.NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}", new Object[]{JacksonUtils.toJson(this.beatInfo), var11.getErrCode(), var11.getErrMsg()});
                }
                // 新创建任务
                BeatReactor.this.executorService.schedule(BeatReactor.this.new BeatTask(this.beatInfo), nextTime, TimeUnit.MILLISECONDS);
            }
        }
    }

take方法(延迟任务实现)

take方法通过lock来实现任务获取的线程安全性,通过一个死循环来使申请任务的工作线程等待,直到获取到任务。获取任务可以分为以下几步:

获取锁。
若队列为空,则当前工作线程等待,否则进行下一步。
获取队列头结点,判断该节点任务的延迟时间是否还有剩余,时间到则返回该任务节点并调整堆,否则执行下一步。
判断leader是否为空,为空说明该任务已被其它工作线程获取,陷入等待,否则执行下一步。
等任务执行时间到,然后获得该任务,直接执行。
唤醒下一个等待的工作线程来获取任务,释放锁。

        public RunnableScheduledFuture<?> take() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                for (;;) {            // 死循环,直到获得任务为止
                    RunnableScheduledFuture<?> first = queue[0];
                    if (first == null)
                        available.await();    // 队列为空,工作线程等待
                    else {
                        long delay = first.getDelay(NANOSECONDS);
                        if (delay <= 0)                  // 任务执行时间到了  
                            return finishPoll(first);    // 任务出队,然后调整堆
                        // 任务执行时间未到
                        first = null; // don't retain ref while waiting
                        if (leader != null)      // 该任务已被其它工作线程锁定,等待
                            available.await();
                        else {                    // 可以获取任务
                            Thread thisThread = Thread.currentThread();
                            leader = thisThread;
                            try {
                                // 等任务执行时间到,然后执行
                                available.awaitNanos(delay);
                            } finally {
                                if (leader == thisThread)
                                    leader = null;
                            }
                        }
                    }
                }
            } finally {
                if (leader == null && queue[0] != null)
                    available.signal();                // 唤醒其它工作线程来获取任务
                lock.unlock();
            }
        }
posted @ 2022-01-12 20:03  倔强的老铁  阅读(1094)  评论(0编辑  收藏  举报