在Actor的编程模型中,Actor之间主要通过消息进行信息传递。因此,很少发生多个Actor需要访问一个共享变量的情况。但在实际开发中,这种情况很难完全避免。如果多个Actor需要对同一个共享变量进行读写时,如何保证线程安全呢?
在Akka中,使用一种叫做Agent的组件来实现这个功能。一个Agent提供了对一个变量的异步更新。当一个Actor希望改变Agent的值时,它会向这个Agent下发一个动作(action)。当多个Actor同时改变Agent时,这些action将会在ExecutionContext中被并发调度执行。在任何时刻,一个Agent最多只能执行一个action,对于某一个线程来说,它执行action的顺序与它的发生顺序一致,但对于不同线程来说,这些action可能会交织在一起。
Agent的修改可以使用两个方法send()或者alter()。它们都可以向Agent发送一个修改动作。但是send()方法没有返回值,而alter()方法会返回一个Future对象便于跟踪Agent的执行。
下面模拟一个场景:有10个Actor,它们一起对一个Agent执行累加操作,每个agent累加10000次,如果没有意外,那么agent最终的值将是100000,如果Actor间的调度出现问题,那么这个值可能小于100000。
public class CounterActor extends UntypedActor {
Mapper<Integer, Integer> addMapper = new Mapper<Integer, Integer>() {
public Integer apply(Integer i) {
return i + 1;
};
};
@Override
public void onReceive(Object msg) throws Exception {
if(msg instanceof Integer) {
for(int i=0; i<10000; i++) {
Future<Integer> f = AgentDemo.counterAgent.alter(addMapper);
AgentDemo.futures.add(f);
}
getContext().stop(getSelf());
} else
unhandled(msg);
}
}
上述代码定义了一个累加的Actor:CounterActor,且定义了累计动作 action addMapper。它的作用是对Agent的值进行修改。
CounterActor的消息处理函数onReceive()中,对全局counterAgent进行累加操作,alter()指定了累加动作addMapper。由于我们希望在将来知道累加行为是否完成,因此在这里将返回Future对象进行收集。完成任务后,Actor自行退出。
程序的主函数如下:
public class AgentDemo {
public static Agent<Integer> counterAgent = Agent.create(0, ExecutionContexts.global());
static ConcurrentLinkedDeque<Future<Integer>> futures = new ConcurrentLinkedDeque<Future<Integer>>();
public static void main(String[] args) {
final ActorSystem system = ActorSystem.create("agentdemo", ConfigFactory.load("samplehello.conf"));
ActorRef[] counter = new ActorRef[10];
for(int i=0; i<counter.length; i++) {
counter[i] = system.actorOf(Props.create(CounterActor.class),"counter_" + i);
}
final Inbox inbox = Inbox.create(system);
for(int i=0; i<counter.length; i++) {
inbox.send(counter[i], 1);
inbox.watch(counter[i]);
}
int closeCount = 0;
//等待所有Actor全部结束
while(true) {
Object msg = inbox.receive(Duration.create(1, TimeUnit.SECONDS));
if(msg instanceof Terminated) {
closeCount++;
if(closeCount == counter.length) {
break;
}
} else {
System.out.println(msg);
}
}
//等待所有的累加线程完成,因为他们都是异步的
Futures.sequence(futures, system.dispatcher()).onComplete(
new OnComplete<Iterable<Integer>>() {
@Override
public void onComplete(Throwable arg0,
Iterable<Integer> arg1) throws Throwable {
System.out.println("counterAgent=" + counterAgent.get());
system.shutdown();
}
}, system.dispatcher());
}
}
上述代码中,创建了10个CounterActor对象。使用Inbox与CounterActor进行通信。第14行的消息将触发CounterActor进行累加操作。第20~30行系统将等待所有10个CounterActor运行结束。执行完成后,我们便已经收集了所有的Future。在第32行,将所有的Future进行串行组合(使用sequence()方法),构造了一个整体的Future,并为它创建onComplete()回调函数。在所有的Agent操作执行完成后,onComplete()方法就会被调用(第35行)。在这个例子中,简单地输出最终的counterAgent值,并关闭系统。
执行上述程序,得到结果:
counterAgent=100000