实现运行在独立线程池的调度功能,基于Spring和Annotation
使用Spring的注解(@Scheduled)声明多个调度的时候,由于其默认实现机制,将导致多个调度方法之间相互干扰(简单理解就是调度不按配置的时间点执行).
为了解决该问题尝试了修改线程池大小,但是治标不治本,原因就是Spring注解方式都在一个相同的线程池抢夺线程资源并且还存在线性执行的问题(这个不是很确定,但是感觉存在这问题).
由于以上原因,决定自行实现简单的调度机制,目前该机制已经上线,经过几次修正,截止目前已经稳定的为系统提供服务.
实现多线程互不干扰的调度需要解决如下几个关键点:
1. 调度程序核心机制怎么实现(如何让某个方法在合适的时间点执行)?
2. 如何保证调度之间相互独立执行(关键问题)?
3. 通过什么方式来配置调度相关信息?
针对以上几个点,采取了如下方式:
1. 调度核心使用jdk的ScheduledExecutorService接口实现
2. 基于1的实现方式,为每个调度获取不同的线程池,并将相关信息登记入册,以供后续使用
3. 通过注解方式声明调度的相关信息,包括间隔时间、初始化执行时间、时间间隔单位等基本属性
首先,来看第一个知识点ScheduledExecutorService,如果您了解该接口,可以跳过该段.
jdk其实自带了调度工具叫做java.util.concurrent.ScheduledExecutorService.通过该工具可以实现与spring scheduled和quartz相同的调度功能,并且可灵活控制.所以决定使用该方式进行尝试.
查看该接口,可以发现一共定义了几个方法
这里我只关心最后一个方法,用来实现上一次调度完成后多久再次执行
有了这个就好办了,直接将原有的注解去掉,在合适的地方通过java.util.concurrent.Executors.newSingleThreadExecutor()方法获取ScheduledExecutorService接口实例.
具体参数不做过多解释,只简单说说第一个参数Runnable command,这个参数传递的就是我们具体要执行的代码逻辑,需要将调度代码放在实现了Runnable接口的实例中的run方法中(有点绕).其实有更好的办法,直接使用一个叫做ScheduledMethodRunnable的类,将某个方法包装成Runnable接口实现类也可以.
ScheduledMethodRunnable类的实现原理就是通过反射动态调用,但是这里要求所包装的方法不能有返回值和参数.
有了以上信息,实现调度就没问题了,通过制定间隔执行时间,就可以达到定期执行的目的了.
然后,来看第二个知识点,这个就比较简单了,为每个调度单独创建一个线程池,并将各种信息登记入册
只需要两部就可以完成:
1 ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);//Executors.newSingleThreadScheduledExecutor()也可以 2 ScheduledFuture<?> sf = ses.scheduleWithFixedDelay(runnableMethod, 初始时间,间隔时间, 时间单位);
最后,来看如何配置,这里我们是基于Spring实现的机制,因此学习了与Spring原生调度相同的注解方式
首先定义注解,直接看代码不解释
然后定义一个注解处理器,处理器负责解析注解并为注解的方法创建调度.
这里的重点是Spring的几个接口BeanPostProcessor, DisposableBean, ApplicationListener<ContextRefreshedEvent>.
我们需要在Bean注册的过程中发现实现了目标注解的所有方法并一一记录,在Spring容器加载完成的时候开始执行所有记录下来的方法,最终在Spring容器释放的时候关闭登记的所有调度处理程序(必须关闭,否则线程不释放).
BeanPostProcessor:Spring容器初始化过程中扫描到Bean会通知该接口实现类
ApplicationListener<ContextRefreshedEvent>:会接受Spring容器发起的ContextRefreshedEvent事件,收到该事件说明容器已经初始化完成,我们可以继续做后续工作.第一版的时候没有使用该事件,直接在BeanPostProcessor接口中启动了调度,导致了各种奇葩问题出现,之后才将具体的调度启动逻辑挪到这里(spring调度也是这么干的,抄袭抄袭)
DisposableBean:Bean释放的接口,当应用容器关闭的时候会触发该接口,我们需要释放已经启动的相关调度.这里也踩过坑,因为开始不知道释放ScheduledExecutorService实例,导致应用容器残留线程在内存中,产生了一大堆奇怪问题,最后找到这里(才发现是未释放之前启动的线程池造成的)
不要忘了将实现了以上接口的类注册到Spring容器中,xml配置也好注解配置也罢,加载上为目的.
最后附上相关源码:
3 import static java.lang.annotation.ElementType.METHOD; 4 import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 6 import java.lang.annotation.Documented; 7 import java.lang.annotation.Retention; 8 import java.lang.annotation.Target; 9 import java.util.concurrent.TimeUnit; 10 11 @Documented 12 @Retention(RUNTIME) 13 @Target(METHOD) 14 public @interface AdvanceDelayScheduled { 15 16 public long initialDelay() default 0; 17 18 public long fixedDelay(); 19 20 public TimeUnit timeUnit() default TimeUnit.MILLISECONDS; 21 }
1 import java.lang.reflect.Method; 2 import java.lang.reflect.Modifier; 3 import java.util.ArrayList; 4 import java.util.Collections; 5 import java.util.List; 6 import java.util.Map; 7 import java.util.Set; 8 import java.util.concurrent.ConcurrentHashMap; 9 import java.util.concurrent.Executors; 10 import java.util.concurrent.ScheduledExecutorService; 11 import java.util.concurrent.ScheduledFuture; 12 import java.util.concurrent.TimeUnit; 13 14 import org.apache.log4j.Logger; 15 import org.springframework.aop.support.AopUtils; 16 import org.springframework.beans.BeansException; 17 import org.springframework.beans.factory.DisposableBean; 18 import org.springframework.beans.factory.config.BeanPostProcessor; 19 import org.springframework.context.ApplicationListener; 20 import org.springframework.context.annotation.Scope; 21 import org.springframework.context.event.ContextRefreshedEvent; 22 import org.springframework.core.MethodIntrospector; 23 import org.springframework.core.annotation.AnnotationUtils; 24 import org.springframework.scheduling.support.ScheduledMethodRunnable; 25 import org.springframework.stereotype.Component; 26 import org.springframework.util.Assert; 27 import org.springframework.util.ReflectionUtils; 28 29 @Component 30 @Scope("singleton") 31 public class AdvanceDelayScheduledProcessor 32 implements BeanPostProcessor, DisposableBean, ApplicationListener<ContextRefreshedEvent> { 33 34 private static final Logger logger = Logger.getLogger(AdvanceDelayScheduledProcessor.class); 35 private final Set<Class<?>> nonAnnotatedClasses = Collections 36 .newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>(64)); 37 38 @Override 39 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { 40 Class<?> targetClass = AopUtils.getTargetClass(bean); 41 42 if (!this.nonAnnotatedClasses.contains(targetClass)) { 43 Map<Method, Set<AdvanceDelayScheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass, 44 new MethodIntrospector.MetadataLookup<Set<AdvanceDelayScheduled>>() { 45 @Override 46 public Set<AdvanceDelayScheduled> inspect(Method method) { 47 Set<AdvanceDelayScheduled> scheduledMethods = AnnotationUtils 48 .getRepeatableAnnotations(method, AdvanceDelayScheduled.class); 49 return (!scheduledMethods.isEmpty() ? scheduledMethods : null); 50 } 51 }); 52 if (annotatedMethods.isEmpty()) { 53 this.nonAnnotatedClasses.add(targetClass); 54 if (logger.isTraceEnabled()) { 55 logger.trace("No @AdvanceDelayScheduled annotations found on bean class: " + bean.getClass()); 56 } 57 } else { 58 // Non-empty set of methods 59 for (Map.Entry<Method, Set<AdvanceDelayScheduled>> entry : annotatedMethods.entrySet()) { 60 Method method = entry.getKey(); 61 for (AdvanceDelayScheduled ads : entry.getValue()) { 62 regScheduledItem(ads, method, bean); 63 } 64 } 65 logger.info("发现调度@AdvanceDelayScheduled:" + annotatedMethods.size() + " methods processed on bean '" 66 + beanName + "': " + annotatedMethods); 67 } 68 } 69 return bean; 70 } 71 72 @Override 73 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { 74 return bean; 75 } 76 77 /** 78 * 注册调度到列表,在spring加载完成后统一启动 79 * 80 * @param scheduled 81 * @param method 82 * @param bean 83 */ 84 protected void regScheduledItem(AdvanceDelayScheduled scheduled, Method method, Object bean) { 85 86 Assert.isTrue(void.class == method.getReturnType(), 87 "Only void-returning methods may be annotated with @AdvanceDelayScheduled"); 88 Assert.isTrue(method.getParameterTypes().length == 0, 89 "Only no-arg methods may be annotated with @AdvanceDelayScheduled"); 90 91 if (AopUtils.isJdkDynamicProxy(bean)) { 92 try { 93 // Found a @AdvanceDelayScheduled method on the target class for 94 // this JDK 95 // proxy -> 96 // is it also present on the proxy itself? 97 method = bean.getClass().getMethod(method.getName(), method.getParameterTypes()); 98 } catch (SecurityException ex) { 99 ReflectionUtils.handleReflectionException(ex); 100 } catch (NoSuchMethodException ex) { 101 throw new IllegalStateException(String.format( 102 "@AdvanceDelayScheduled method '%s' found on bean target class '%s' but not " 103 + "found in any interface(s) for a dynamic proxy. Either pull the " 104 + "method up to a declared interface or switch to subclass (CGLIB) " 105 + "proxies by setting proxy-target-class/proxyTargetClass to 'true'.", 106 method.getName(), method.getDeclaringClass().getSimpleName())); 107 } 108 } else if (AopUtils.isCglibProxy(bean)) { 109 // Common problem: private methods end up in the proxy instance, not 110 // getting 111 // delegated. 112 if (Modifier.isPrivate(method.getModifiers())) { 113 throw new IllegalStateException(String.format( 114 "@AdvanceDelayScheduled method '%s' found on CGLIB proxy for target class '%s' but cannot " 115 + "be delegated to target bean. Switch its visibility to package or protected.", 116 method.getName(), method.getDeclaringClass().getSimpleName())); 117 } 118 } 119 120 ScheduledMethodRunnable runnable = new ScheduledMethodRunnable(bean, method); 121 122 // 注册到调度列表,等待加载完成再执行 123 scheduledItems.add(new ScheduledItem(method.getName(), runnable, scheduled)); 124 125 } 126 127 private List<ScheduledItem> scheduledItems = new ArrayList<>(); 128 129 @Override 130 public void destroy() throws Exception { 131 // 释放所有调度任务 132 scheduledItems.forEach(item -> { 133 try { 134 logger.info("关闭ScheduledExecutorService:" + item.getScheduledExecutorService()); 135 item.getScheduledExecutorService().shutdown(); 136 try { 137 if (!item.getScheduledExecutorService().awaitTermination(5, TimeUnit.SECONDS)) 138 item.getScheduledExecutorService().shutdownNow(); 139 } catch (Exception e) { 140 item.getScheduledExecutorService().shutdownNow(); 141 throw e; 142 } 143 } catch (Exception e) { 144 logger.error("关闭ScheduledExecutorService失败," + e.getMessage(), e); 145 } 146 }); 147 } 148 149 @Override 150 public void onApplicationEvent(ContextRefreshedEvent event) { 151 // 在这里启动所有调度 152 if (event.getApplicationContext().getParent() != null) { 153 return; 154 } 155 156 scheduledItems.forEach(item -> { 157 try { 158 AdvanceDelayScheduled ads = item.getAdvanceDelayScheduled(); 159 // 每次都重新创建一个调度器 160 ScheduledExecutorService ses = Executors.newScheduledThreadPool(item.getPoolSize()); 161 ScheduledFuture<?> sf = ses.scheduleWithFixedDelay(item.getRunnable(), ads.initialDelay(), 162 ads.fixedDelay(), ads.timeUnit()); 163 item.setScheduledExecutorService(ses); 164 item.setScheduledFuture(sf); 165 } catch (Exception e) { 166 logger.error("启动调度失败,调度名称[" + item.getName() + "]," + e.getMessage(), e); 167 } 168 }); 169 170 } 171 172 protected class ScheduledItem { 173 private String name; 174 private Runnable runnable; 175 private AdvanceDelayScheduled advanceDelayScheduled; 176 private int poolSize = 1; 177 private ScheduledExecutorService scheduledExecutorService; 178 private ScheduledFuture<?> scheduledFuture; 179 180 public ScheduledItem(String name, Runnable runnable, AdvanceDelayScheduled advanceDelayScheduled) { 181 super(); 182 this.name = name; 183 this.runnable = runnable; 184 this.advanceDelayScheduled = advanceDelayScheduled; 185 } 186 187 public ScheduledItem() { 188 super(); 189 // TODO Auto-generated constructor stub 190 } 191 192 /** 193 * @return the name 194 */ 195 public String getName() { 196 return name; 197 } 198 199 /** 200 * @param name 201 * the name to set 202 */ 203 public void setName(String name) { 204 this.name = name; 205 } 206 207 /** 208 * @return the runnable 209 */ 210 public Runnable getRunnable() { 211 return runnable; 212 } 213 214 /** 215 * @param runnable 216 * the runnable to set 217 */ 218 public void setRunnable(Runnable runnable) { 219 this.runnable = runnable; 220 } 221 222 /** 223 * @return the advanceDelayScheduled 224 */ 225 public AdvanceDelayScheduled getAdvanceDelayScheduled() { 226 return advanceDelayScheduled; 227 } 228 229 /** 230 * @param advanceDelayScheduled 231 * the advanceDelayScheduled to set 232 */ 233 public void setAdvanceDelayScheduled(AdvanceDelayScheduled advanceDelayScheduled) { 234 this.advanceDelayScheduled = advanceDelayScheduled; 235 } 236 237 /** 238 * @return the poolSize 239 */ 240 public int getPoolSize() { 241 return poolSize; 242 } 243 244 /** 245 * @param poolSize 246 * the poolSize to set 247 */ 248 public void setPoolSize(int poolSize) { 249 this.poolSize = poolSize; 250 } 251 252 /** 253 * @return the scheduledFuture 254 */ 255 public ScheduledFuture<?> getScheduledFuture() { 256 return scheduledFuture; 257 } 258 259 /** 260 * @param scheduledFuture 261 * the scheduledFuture to set 262 */ 263 public void setScheduledFuture(ScheduledFuture<?> scheduledFuture) { 264 this.scheduledFuture = scheduledFuture; 265 } 266 267 /** 268 * @return the scheduledExecutorService 269 */ 270 public ScheduledExecutorService getScheduledExecutorService() { 271 return scheduledExecutorService; 272 } 273 274 /** 275 * @param scheduledExecutorService 276 * the scheduledExecutorService to set 277 */ 278 public void setScheduledExecutorService(ScheduledExecutorService scheduledExecutorService) { 279 this.scheduledExecutorService = scheduledExecutorService; 280 } 281 282 } 283 284 }