行动还是不行动? 就是那个问题!
即使使用Java 6和Java 7进行并发更新,Java语言也不能使并行编程特别容易。 Java线程, synchronized
块, wait
/ notify
和java.util.concurrent
包都有它们的位置,但是Java开发人员迫切需要满足多核系统的能力,他们正在寻求其他语言的先驱技术。 actor模型就是这样一种技术,在Erlang,Groovy和Scala中实现。 对于希望尝试使用actor但继续编写Java代码的开发人员,本文介绍了μJavaActors库。
μJavaActors库是一个紧凑的库,用于在Java平台上实现基于actor的系统(μ表示希腊字母Mμ,表示“ micro”)。 在本文中,我使用μJavaActors来发现actor如何以常见的设计模式工作,例如Producer / Consumer和Map / Reduce。
Java平台上的Actor并发
名字叫什么? 任何其他名称的Actor也将起作用!
基于Actor的系统通过实现消息传递方案,使并行处理更易于编码。 在这种方案中,系统中的每个参与者都可以接收消息。 执行消息要求的操作; 并向其他参与者(包括他们自己)发送消息,以执行复杂的操作序列。 actor之间的所有消息都是异步的 ,这意味着发送方在收到任何答复之前会继续进行处理。 因此,演员的一生可能会花费在接收和处理消息的无限循环中。
当使用多个参与者时,独立的活动可以轻松地分布在可以并行执行消息的多个线程(进而是处理器)之间。 通常,每个参与者都在单独的线程上处理消息,从而允许并行执行多达参与者的数量。 一些参与者系统会为参与者静态地分配线程。 其他对象(如本文介绍的系统)将对其进行动态分配。
μJavaActors简介
μJavaActors是actor系统的简单Java实现。 μJavaActors大约有1200行代码,虽然小巧但功能强大。 在下面的练习中,您将学习如何使用μJavaActors动态创建和管理actor并向它们传递消息。
μJavaActors基于三个核心接口构建:
- 消息是演员之间发送的消息。
Message
是三个(可选)值和某些行为的容器:-
source
是发送演员。 -
subject
是定义消息含义的字符串(也称为command )。 -
data
是消息的任何参数数据; 通常是地图,列表或数组。 参数可以是要处理的数据和/或其他与之交互的参与者。 -
subjectMatches()
检查消息主题是否匹配字符串或正则表达式。
DefaultMessage
。 -
- ActorManager是演员的经理。 它负责分配线程(从而分配处理器)给参与者以处理消息。
ActorManager
具有以下关键行为或特征:-
createActor()
创建一个actor并将其与此管理器关联。 -
startActor()
启动一个actor。 -
detachActor()
停止一个actor并将其与该管理器解除关联。 -
send()/broadcast()
将消息发送给演员,一组演员,类别的任何演员或所有演员。
ActorManager
,但是如果要管理多个线程和/或Actor池,则可以使用多个。 此接口的默认实现是DefaultActorManager
。 -
- Actor是一次处理一个消息的执行单元。
Actor
具有以下关键行为或特征:- 每个actor都有一个
name
,每个ActorManager
的name
必须唯一。 - 每个演员都属于一个
category
; 类别是向一组参与者中的一个成员发送消息的一种方式。 演员一次只能属于一个类别。 - 每当
ActorManager
可以提供一个线程来执行actor时,ActorManager
调用receive()
。 仅当参与者的消息存在时才调用它。 为了最有效,演员应该快速处理消息,并且不要输入长时间的等待(例如人工输入)。 -
willReceive()
允许willReceive()
过滤潜在的消息主题。 -
peek()
允许演员和其他人查看是否有待处理消息,可能是针对选定主题的。 -
remove()
允许参与者和其他参与者删除或取消任何尚未处理的消息。 -
getMessageCount()
允许getMessageCount()
和其他getMessageCount()
获取待处理消息的数量。 -
getMaxMessageCount()
允许getMaxMessageCount()
限制支持的待处理消息数量。 此方法可用于防止发送失控。
AbstractActor
的抽象类,actor实现基于该抽象类。 - 每个actor都有一个
图1显示了参与者之间的关系。 每个演员可以向其他演员发送消息。 消息保存在消息队列中(也称为邮箱 ;概念上每个ActorManager
一个邮箱 ),当ActorManager
看到有可用的线程来处理消息时,该消息将从队列中删除并传递给在下面运行的actor处理该消息的线程。
图1.参与者之间的关系
![演员通过线程传递的消息发送给演员](https://i-blog.csdnimg.cn/blog_migrate/1013b4e7d504060e1bbd94302bb30134.png)
使用μJavaActors并行执行
戏就是这个!
现在您可以开始使用μJavaActors进行并行执行了。 您将从创建一组参与者开始。 这些演员很简单,因为他们所做的只是延迟一小段时间,然后将消息发送给其他演员。 这样做的结果是造成大量的消息,随着时间的流逝而安静下来,并最终停止。 在下面的演示中,您将首先看到如何创建角色,然后逐步分配它们以处理消息。
有两种消息类型:
-
initialization
(init
)使参与者进行初始化。 每个演员仅发送一次。 -
repeat
会导致参与者发送N-1条消息,其中N是传入的消息参数。
清单1中的TestActor
实现了从AbstractActor
继承的抽象方法。 activate
和deactivate
方法将其生命周期告知参与者。 在此示例中,无需执行其他任何操作。 首次创建actor时会调用runBody
方法,然后再接收任何消息。 它通常用于将第一个消息引导到参与者。 当actor将要接收消息时,将调用testMessage
方法; 演员可以在这里拒绝或接受消息。 在这种情况下, testMessage
使用继承的testMessage
方法来测试接受度; 因此,所有消息都被接受。
清单1. TestActor
class TestActor extends AbstractActor {
@Override
public void activate() {
super.activate();
}
@Override
public void deactivate() {
super.deactivate();
}
@Override
protected void runBody() {
sleeper(1); // delay up to 1 second
DefaultMessage dm = new DefaultMessage("init", 8);
getManager().send(dm, null, this);
}
@Override
protected Message testMessage() {
return super.testMessage();
}
当actor收到消息时,将调用清单2中所示的loopBody
方法。 经过短暂的延迟以模拟某些常规处理后,将处理该消息。 如果消息是“ repeat
”,则参与者基于count
参数开始发送更多N-1条消息的过程。 通过调用参与者管理者的send
方法将消息发送给随机参与者。
清单2. loopBody()
@Override
protected void loopBody(Message m) {
sleeper(1);
String subject = m.getSubject();
if ("repeat".equals(subject)) {
int count = (Integer) m.getData();
if (count > 0) {
DefaultMessage dm = new DefaultMessage("repeat", count - 1);
String toName = "actor" + rand.nextInt(TEST_ACTOR_COUNT);
Actor to = testActors.get(toName);
getManager().send(dm, this, to);
}
}
如果消息是“ init
”,则参与者通过向随机选择的参与者或common
类别的参与者发送两组消息来启动repeat
消息序列。 某些消息可以立即处理(实际上是在actor准备好接收它们并且有线程可用时立即处理); 其他人必须等到未来几秒钟才能运行。 这种延迟的消息处理对于此示例而言并不重要,但是可以用于为长时间运行的过程(例如,等待用户输入或可能是对网络请求到达的响应)进行轮询。
清单3.初始化序列
else if ("init".equals(subject)) {
int count = (Integer) m.getData();
count = rand.nextInt(count) + 1;
for (int i = 0; i < count; i++) {
DefaultMessage dm = new DefaultMessage("repeat", count);
String toName = "actor" + rand.nextInt(TEST_ACTOR_COUNT);
Actor to = testActors.get(toName);
getManager().send(dm, this, to);
dm = new DefaultMessage("repeat", count);
dm.setDelayUntil(new Date().getTime() + (rand.nextInt(5) + 1) * 1000);
getManager().send(dm, this, "common");
}
}
否则,该消息是不合适的,并报告错误:
else {
System.out.printf("TestActor:%s loopBody unknown subject: %s%n",
getName(), subject);
}
}
}
主程序包含清单4中的代码,该代码在common
类别中创建两个参与者,在default
类别中创建五个参与者,然后启动它们。 然后main
最多等待120秒( sleeper
等待其参数值乘以〜1000ms),并定期显示进度消息。
清单4. createActor,startActor
DefaultActorManager am = DefaultActorManager.getDefaultInstance();
:
Map<String, Actor> testActors = new HashMap<String, Actor>();
for (int i = 0; i < 2; i++) {
Actor a = am.createActor(TestActor.class, "common" + i);
a.setCategory("common");
testActors.put(a.getName(), a);
}
for (int i = 0; i < 5; i++) {
Actor a = am.createActor(TestActor.class, "actor" + i);
testActors.put(a.getName(), a);
}
for (String key : testActors.keySet()) {
am.startActor(testActors.get(key));
}
for (int i = 120; i > 0; i--) {
if (i < 10 || i % 10 == 0) {
System.out.printf("main waiting: %d...%n", i);
}
sleeper(1);
}
:
am.terminateAndWait();
跟踪输出
为了了解刚刚执行的过程,让我们看一下参与者的一些跟踪输出。 (请注意,由于随机数用于计数和延迟,因此每次执行的输出可能会有所不同。)在清单5中,您将看到在程序开始附近发生的消息。 左列(在方括号中)是正在执行的线程的名称。 在此运行中,有25个线程可用于处理消息。 该行的其余部分是(摘要)跟踪输出,显示接收到的每个消息。 请注意,重复计数(即参数数据)会随着时间减少。 (还要注意,以actor
开头的线程名称与actor的名称无关。)
清单5.跟踪输出:程序启动
[main ] - main waiting: 120...
[actor17 ] - TestActor:actor4 repeat(4)
[actor0 ] - TestActor:actor1 repeat(4)
[actor10 ] - TestActor:common1 repeat(4)
[actor1 ] - TestActor:actor2 repeat(4)
[actor3 ] - TestActor:actor0 init(8)
[actor22 ] - TestActor:actor3 repeat(4)
[actor17 ] - TestActor:actor4 init(7)
[actor20 ] - TestActor:common0 repeat(4)
[actor24 ] - TestActor:actor0 repeat(4)
[actor0 ] - TestActor:actor1 init(3)
[actor1 ] - TestActor:actor2 repeat(4)
[actor20 ] - TestActor:common0 repeat(4)
[actor17 ] - TestActor:actor4 repeat(4)
[actor17 ] - TestActor:actor4 repeat(3)
[actor0 ] - TestActor:actor1 repeat(8)
[actor10 ] - TestActor:common1 repeat(4)
[actor24 ] - TestActor:actor0 repeat(8)
[actor0 ] - TestActor:actor1 repeat(8)
[actor24 ] - TestActor:actor0 repeat(7)
[actor22 ] - TestActor:actor3 repeat(4)
[actor1 ] - TestActor:actor2 repeat(3)
[actor20 ] - TestActor:common0 repeat(4)
[actor22 ] - TestActor:actor3 init(5)
[actor24 ] - TestActor:actor0 repeat(7)
[actor10 ] - TestActor:common1 repeat(4)
[actor17 ] - TestActor:actor4 repeat(8)
[actor1 ] - TestActor:actor2 repeat(3)
[actor17 ] - TestActor:actor4 repeat(8)
[actor0 ] - TestActor:actor1 repeat(8)
[actor10 ] - TestActor:common1 repeat(4)
[actor22 ] - TestActor:actor3 repeat(8)
[actor0 ] - TestActor:actor1 repeat(7)
[actor1 ] - TestActor:actor2 repeat(3)
[actor0 ] - TestActor:actor1 repeat(3)
[actor20 ] - TestActor:common0 repeat(4)
[actor24 ] - TestActor:actor0 repeat(7)
[actor24 ] - TestActor:actor0 repeat(6)
[actor10 ] - TestActor:common1 repeat(8)
[actor17 ] - TestActor:actor4 repeat(7)
在清单6中,您将看到重复次数越来越少时在程序末尾出现的消息。 如果您正在观察该程序的执行,您将能够观察到行生成速度的逐渐降低。 这是因为生成的消息数随时间减少。 给定足够的等待时间,发送给参与者的消息将完全停止(如清单6所示的common
参与者)。 请注意,消息处理合理地分布在可用线程之间,并且没有任何特定参与者绑定到任何特定线程。
清单6.跟踪输出:程序结束
[main ] - main waiting: 20...
[actor0 ] - TestActor:actor4 repeat(0)
[actor2 ] - TestActor:actor2 repeat(1)
[actor3 ] - TestActor:actor0 repeat(0)
[actor17 ] - TestActor:actor4 repeat(0)
[actor0 ] - TestActor:actor1 repeat(2)
[actor3 ] - TestActor:actor2 repeat(1)
[actor14 ] - TestActor:actor1 repeat(2)
[actor5 ] - TestActor:actor4 repeat(0)
[actor14 ] - TestActor:actor2 repeat(0)
[actor21 ] - TestActor:actor1 repeat(0)
[actor14 ] - TestActor:actor0 repeat(1)
[actor14 ] - TestActor:actor4 repeat(0)
[actor5 ] - TestActor:actor2 repeat(1)
[actor5 ] - TestActor:actor4 repeat(1)
[actor6 ] - TestActor:actor1 repeat(1)
[actor5 ] - TestActor:actor3 repeat(0)
[actor6 ] - TestActor:actor2 repeat(1)
[actor4 ] - TestActor:actor0 repeat(0)
[actor5 ] - TestActor:actor4 repeat(1)
[actor12 ] - TestActor:actor1 repeat(0)
[actor20 ] - TestActor:actor2 repeat(2)
[main ] - main waiting: 10...
[actor7 ] - TestActor:actor4 repeat(2)
[actor23 ] - TestActor:actor1 repeat(0)
[actor13 ] - TestActor:actor2 repeat(1)
[actor8 ] - TestActor:actor0 repeat(0)
[main ] - main waiting: 9...
[actor2 ] - TestActor:actor1 repeat(0)
[m