实现运行在独立线程池的调度功能,基于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 }

 

posted @ 2017-08-04 14:56  relinson  阅读(737)  评论(0编辑  收藏  举报