背景
项目中有大量任务需要放入线程池中执行,以提高程序执行效率。 这些任务的主要区别是对不同资源执行不同操作。 于是就有,根据不同资源创建不同的Callable实例,如下示例
class NodesCallable implements Callable {
private CountDownLatch countDownLatch;
public NodesCallable(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public Object call() throws Exception {
getNodes();
return true;
}
}
class PoolsCallable implements Callable {
private CountDownLatch countDownLatch;
public PoolsCallable(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public Object call() throws Exception {
getPools();
return true;
}
}
项目中这类型的资源大概有十几个,所以需要创建十几个Callable类,存储大量重复代码,不美观。
后面发现,可以用函数式方程优化掉,不用创建这么多Callable类,但实例化时还是需要根据不同资源类型重写不同的方法,仍存在大量重复代码。
经思考,发现可以使用注解+反射的形式将Callable这块代码给优化掉。 下面展示操作。
如何使用
- 在需要开启多线程的方法上添加@ConfigPull注解
@ConfigPull public void executeBashCommand() throws ConfigurationCollectionException { }
- 调用ConfigPullUtil.configPullExecute()方法
ConfigPullUtil.configPullExecute(F5LoadBalance.class, countDownLatch, this);
获取被注解标记的方法
通过反射获取该类的所有方法,判断方法上是否存在@ConfigPull注解 若存在注解,则添加进队列。
private static List<Method> getMethods(Class<? extends Object> configClass) {
List<Method> configMethods = new ArrayList<>();
Method[] methods = configClass.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(ConfigPull.class)) {
configMethods.add(method);
}
}
return configMethods;
}
构造Callable实例并放入线程池执行,并等待所有任务执行完成
遍历前面得到的包含所有被注解标识的方法队列 构建Callable实例,重写call()方法,主要是调用当前method 将Callable实例添加到线程池中执行,并将执行结果添加至Futures队列中,以备后续检查
private static List<Future<Boolean>> execute(List<Method> configMethods, CountDownLatch countDownLatch, Object instance) {
List<Future<Boolean>> futures = new ArrayList<>();
for (Method method : configMethods) {
ConfigCallable callable = new ConfigCallable(countDownLatch) {
@Override
public Object call() throws ConfigurationCollectionException {
try {
method.invoke(instance);
return true;
} catch (InvocationTargetException | IllegalAccessException e) {
ConfigurationCollectionException configurationCollectionException = new ConfigurationCollectionException(e.getMessage());
configurationCollectionException.initCause(e);
throw configurationCollectionException;
}
}
};
futures.add(configPullExecutor.submit(callable));
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
Thread.currentThread().interrupt();
}
return futures;
}
检查配置采集结果,有异常则抛出
遍历Futures队列,检查结果。 若遇到异常则抛出 若无异常,则程序执行完毕
private static void checkFutures(List<Future<Boolean>> futures) throws ConfigurationCollectionException {
try {
for (Future<Boolean> future : futures) {
future.get();
}
} catch (ExecutionException e) {
ConfigurationCollectionException configurationCollectionException = new ConfigurationCollectionException(e.getMessage());
configurationCollectionException.initCause(e);
throw configurationCollectionException;
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
Thread.currentThread().interrupt();
}
}
总结
经上述方法,注解+反射的组合,可以达到一个只要方法上添加了注解,程序就会扫描到并将该方法丢进线程池中执行。 而后续的使用也很简单,只需俩步即可实现方法粒度的多线程任务执行器。
|