Future被Akka设计用来处理并发执行结果的数据结构,我们可以通过同步(阻塞)或异步(非阻塞)的方法接受结果。使用Future,我们可以在Actor外部获取某个Actor的消息,在介绍Actor消息发送方式时,我们进行过简单介绍,现在我们先来回忆一下Future的常规用法。
ask发送消息会返回一个Future对象,通过该对象我们可以同步或异步方式处理Actor返回的结果消息。同步方式主要依赖Await类的方法阻塞等待返回值,异步方式则依靠Future对象提供的诸多回调方法。使用ask方法时,我们最好设置一个超时时间,否则一直阻塞严重影响系统的吞吐率和性能。
同步方式代码示例:
public class FutureActor extends AbstractActor {
public static void main(String[] args) {
ActorSystem system = ActorSystem.create("system");
ActorRef futureActor = system.actorOf(Props.create(FutureActor.class), "futureActor");
Timeout timeout = new Timeout(Duration.create(2, TimeUnit.SECONDS));
Future
我们首先定义Timeout用于限制超时,然后使用Patterns.ask给futureActor发送异步请求,返回一个Future对象,该对象封装了futureActor返回的消息,之前我们说过处理消息有两种方式:同步和异步,代码中采用Await.result(同步方式),表明我们会在获取消息上阻塞,直到消息返回才会继续往下执行。这里我们使用sleep方法模拟服务超时,程序会抛出TimeoutException。
java.util.concurrent.TimeoutException: Futures timed out after [2 seconds]
at scala.concurrent.impl.Promise$DefaultPromise.ready(Promise.scala:223)
at scala.concurrent.impl.Promise$DefaultPromise.result(Promise.scala:227)
at scala.concurrent.Await$$anonfun$result$1.apply(package.scala:190)
at scala.concurrent.BlockContext$DefaultBlockContext$.blockOn(BlockContext.scala:53)
at scala.concurrent.Await$.result(package.scala:190)
at scala.concurrent.Await.result(package.scala)
at com.release.util.akka.Future.FutureActor.main(FutureActor.java:26)
在实际项目中,如果ask请求结果不影响程序继续执行,为了尽可能提高程序响应速度,我们或许更喜欢异步方式处理消息,Future类提供了诸多回调函数,例如:onComplete(处理执行完成)、onSuccess(处理执行成功)、onFailure(处理执行失败),修改上述示例:
future.onComplete(new OnComplete
当futureActor及时返回消息,onSuccess方法会执行并接受返回的消息;当程序执行超时或其它错误,会调用onFailure方法并得到异常;大家执行可以发现,无论成功与否,onComplete方法都会调用,它相当于onSuccess和onFailure方法整合。
相信使用过Jdk1.8或以上的朋友,都知道函数式编程的好处,调用清晰、处理简单,方便我们对各类数据进行分析和调整。在这里Future也提供了很多好用的函数式API,方便我们对数据处理。
当我们需要对返回的数据进行处理,并需要返回新的结果时,可以使用map函数。对数据进行处理后,map函数会返回一个新的Future,不影响之前Future的值,是一个全新的Future。例如:
//对actor返回的消息进行处理,返回新的future
Future map = future.map(new Mapper() {
@Override
public String apply(Object msg) {
return "获得futureActor的结果:" + msg;
}
}, system.dispatcher());
try {
String reply = Await.result(map, timeout.duration());
System.out.println(reply);
} catch (Exception e) {
e.printStackTrace();
}
使用map函数,需要new一个Mapper对象,该对象需要两个参数,第一个代表输入类型,第二个代表处理后输出的类型,与apply方法的入参和返回值相对应。
map函数不仅可以作用于一个Future对象,map函数还可以作用于多个Future对象,当我们获取到一系列Future之后,我们可以把它们组合成一个列表,利用map函数进行计算。例如:统计一个班级上姓李的同学数量。
示例:
public class MapActor extends AbstractActor {
public static void main(String[] args) {
String[] names = {"张三", "李四", "王五", "李春花", "李贵妃"};
ActorSystem system = ActorSystem.create();
Timeout timeout = new Timeout(Duration.create(3, TimeUnit.SECONDS));
List> futures = new ArrayList<>();
for (int i = 0; i < names.length; i++) {
ActorRef ref = system.actorOf(Props.create(MapActor.class), "mapActor" + i);
Future future = Patterns.ask(ref, names[i], timeout);
futures.add(future);
}
//把Future列表转换为带有结果列表的Future对象
Future> iterableFuture = Futures.sequence(futures, system.dispatcher());
Future map = iterableFuture.map(new Mapper, Integer>() {
@Override
public Integer apply(Iterable parameter) {
Integer count = 0;
for (Integer o : parameter) {
count += o;
}
return count;
}
}, system.dispatcher());
try {
Integer reply = Await.result(map, timeout.duration());
System.out.println("李姓数量: " + reply);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Receive createReceive() {
return receiveBuilder().match(String.class, s -> getSender().tell(s.startsWith("李") ? 1 : 0, getSelf())).build();
}
}
上述过程中,我们先使用sequence让List集合转换为带有结果列表的Future对象,因为List集合是不能直接使用map方法的,毕竟map方法不属于List,之后使用map方法遍历结果计算,最终返回一个带有我们所需结果的新Future对象。
我们除了使用map函数统计之外,还可以使用fold函数,fold与map函数有所不同,fold函数提供了一个初始值,当我们传一个带有Future的列表时,fold会先使用初始值和列表的第一个值计算,然后把计算的值作为下一次的初始值,以此类推。修改上述代码:
Future fold = Futures.fold(0, futures, new Function2() {
@Override
public Integer apply(Integer v1, Integer v2) throws Exception {
System.out.println(v1+"--->"+v2);
return v1 + v2;
}
}, system.dispatcher());
输出结果:
0--->0
0--->1
1--->0
1--->1
2--->1
李姓数量: 3
当然这里完全可以不需要初始值,所以我们也可以使用reduce函数,使用方式和fold类似,如下:
Future fold = Futures.reduce( futures, new Function2() {
@Override
public Integer apply(Integer v1, Integer v2) throws Exception {
System.out.println(v1+"--->"+v2);
return v1 + v2;
}
}, system.dispatcher());
使用Future可以让我们在Actor外部获取结果,并且可以选择获取结果的方式:异步或者同步,尽可能的利用异步方式获取结果,可以提高系统的吞吐率和响应速度。另外,使用Future,我们还可以使用它所提供的诸多函数式API,方便我们对数据进行处理或计算。