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();
}
}