概述
无差别地申请多个内部接口并聚合所有申请后果,应该有属于它本人的套路,应该将所有多线程的操作屏蔽之,咱们只关怀参数和后果。因而,应该摈弃Callable/FutureTask/Future等这些手工模式,这些代码应该交给框架来实现。
手工模式
何为手工模式,咱们以Callable为例设计申请内部的接口,可能像上面这样子,参数是NumberParam,两个内部接口别离是IntToStringCallable和DoubleToStringCallable,
class IntToStringCallable implements Callable {
private final NumberParam param;
IntToStringCallable(NumberParam numberParam) {
this.param = numberParam;
}
@Override
public String call() {
return Integer.toHexString(param.getAge());
}
}
class DoubleToStringCallable implements Callable {
private final NumberParam param;
DoubleToStringCallable(NumberParam numberParam) {
this.param = numberParam;
}
@Override
public String call() {
return Double.toHexString(param.getMoney());
}
}
如果采纳FutureTask的形式多线程执行这两个接口,可能是这样子的,
FutureTask r1 = new FutureTask<>(new IntToStringCallable(numberParam));
new Thread(r1).start();
FutureTask r2 = new FutureTask<>(new DoubleToStringCallable(numberParam));
new Thread(r2).start();
try {
List ret = new ArrayList<>();
ret.add(r1.get());
ret.add(r2.get());
log.info("ret=" + ret);
} catch (Exception ignore) {
}
须要首先结构FutureTask,而后应用Thread比拟原始的api去执行,当然还能够再简化一下,比方应用Future形式,
ExecutorService threadPool = Executors.newFixedThreadPool(2);
Future r1 = threadPool.submit(new IntToStringCallable(numberParam));
Future r2 = threadPool.submit(new DoubleToStringCallable(numberParam));
try {
List ret = new ArrayList<>();
ret.add(r1.get());
ret.add(r2.get());
log.info("ret=" + ret);
} catch (Exception ignore) {
}
我置信这是一种广泛常见的做法了。这里没有必要持续评论这些做法的问题了。
Java 8之后
Java 8之后有了更加不便的异步编程形式了,不必再辛苦地去写Callable的,一句话就能够表白Callable+FutureTask/…,
CompletableFuture pf = CompletableFuture.supplyAsync(() -> new IntToStringCallable(numberParam).call());
革新之前的做法后果可能就是这个样子了,
CompletableFuture r1 = CompletableFuture.supplyAsync(() -> new IntToStringCallable(numberParam).call());
CompletableFuture r2 = CompletableFuture.supplyAsync(() -> new DoubleToStringCallable(numberParam).call());
try {
List ret = new ArrayList<>();
ret.add(r1.get());
ret.add(r2.get());
log.info("ret=" + ret);
} catch (Exception ignore) {
}
其实能够看进去,这个时候咱们不肯定须要一个Callable了,提供异步的能力是supplyAsync来实现的,咱们只须要失常的入参出参的一般办法就能够了。
Java 8之后再之后
Java 8之后的异步编程形式的确简略了很多,然而在咱们的业务代码中还是呈现了和异步编程相干的无关业务逻辑的事件,可否持续简化呢。本案的设计灵感来自同样Java 8的优良设计——ParallelStream,举个简略的例子,
Arrays.asList("a", "b", "c").parallelStream().map(String::toUpperCase).collect(Collectors.toList());
异步及多线程是ParallelStream来实现的,用户只须要实现String::toUpperCase局部。
本案的设计次要有三个interface来实现,别离是,
public interface MyProvider {
T provide(V v);
}
public interface MyCollector {
void collectList(T t);
List retList();
}
public interface MyStream {
List toList(List> providers, V v);
}
其实MyProvider表白是申请内部接口,MyStream示意一种相似ParallelStream的思维,一种内化异步多线程的操作模式,MyCollector属于外部设计api能够不裸露给用户;
一个改写下面的例子的例子,
@Test
public void testStream() {
MyProvider p1 = new IntToStringProvider();
MyProvider p2 = new DoubleToStringProvider();
List> providers = Arrays.asList(p1, p2);
MyStream myStream = new CollectStringStream();
List strings = myStream.toList(providers, numberParam);
log.info("ret=" + strings);
}
在这个办法内一点异步编程的内容都没有的,用户只须要编程本人关怀的逻辑即可,当然是要依照Provider的思路去写,这或者有一点心智累赘。
这个CollectStringStream帮咱们实现来一些脏活累活,
public List toList(List> myProviders, NumberParam param) {
MyCollector myCollector = new NoMeaningCollector();
List> pfs = new ArrayList<>(myProviders.size());
for (MyProvider provider : myProviders) {
CompletableFuture pf = CompletableFuture.runAsync(() -> myCollector.collectList(provider.provide(param)), executor);
pfs.add(pf);
}
try {
CompletableFuture.allOf(pfs.toArray(new CompletableFuture[0])).get(3, TimeUnit.SECONDS);
} catch (Exception e) {
if (e instanceof TimeoutException) {
pfs.forEach(p -> {
if (!p.isDone()){
p.cancel(true);
}});
}
}
return myCollector.retList();
}
这样看起这个设计又不美了,然而如果有更多的内部接口须要调用,CollectStringStream就显得很有价值了,新退出再多的申请内部接口要改变的代码很少很少,所以这种思维我感觉是值得推广的。
总结
照例附上参考代码,不过值得思考的是咱们如何像优良的代码学习并使用到本人的我的项目中。
参考代码,java-toy