java actor_一个Java actor库,用于并行执行

本文介绍了μJavaActors库,一个用于Java平台的简单并行执行库,基于Actor模型实现。文章讨论了μJavaActors如何通过消息传递实现并发,并展示了如何在生产者/消费者系统和Map/Reduce模式中使用Actor。此外,还通过代码示例和模拟屏幕截图说明了如何动态创建和管理Actor。尽管μJavaActors相对简单,但它能够有效地分配线程处理消息,避免死锁,并提供灵活的行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

行动还是不行动? 就是那个问题!

即使使用Java 6和Java 7进行并发更新,Java语言也不能使并行编程特别容易。 Java线程, synchronized块, wait / notifyjava.util.concurrent包都有它们的位置,但是Java开发人员迫切需要满足多核系统的能力,他们正在寻求其他语言的先驱技术。 actor模型就是这样一种技术,在Erlang,Groovy和Scala中实现。 对于希望尝试使用actor但继续编写Java代码的开发人员,本文介绍了μJavaActors库。

μJavaActors库是一个紧凑的库,用于在Java平台上实现基于actor的系统(μ表示希腊字母Mμ,表示“ micro”)。 在本文中,我使用μJavaActors来发现actor如何以常见的设计模式工作,例如Producer / Consumer和Map / Reduce。

您可以随时下载 μJavaActors库的源代码

Java平台上的Actor并发

名字叫什么? 任何其他名称的Actor也将起作用!

基于Actor的系统通过实现消息传递方案,使并行处理更易于编码。 在这种方案中,系统中的每个参与者都可以接收消息。 执行消息要求的操作; 并向其他参与者(包括他们自己)发送消息,以执行复杂的操作序列。 actor之间的所有消息都是异步的 ,这意味着发送方在收到任何答复之前会继续进行处理。 因此,演员的一生可能会花费在接收和处理消息的无限循环中。

当使用多个参与者时,独立的活动可以轻松地分布在可以并行执行消息的多个线程(进而是处理器)之间。 通常,每个参与者都在单独的线程上处理消息,从而允许并行执行多达参与者的数量。 一些参与者系统会为参与者静态地分配线程。 其他对象(如本文介绍的系统)将对其进行动态分配。

μJavaActors简介

μJavaActors是actor系统的简单Java实现。 μJavaActors大约有1200行代码,虽然小巧但功能强大。 在下面的练习中,您将学习如何使用μJavaActors动态创建和管理actor并向它们传递消息。

μJavaActors基于三个核心接口构建:

  • 消息是演员之间发送的消息。 Message是三个(可选)值和某些行为的容器:
    • source是发送演员。
    • subject是定义消息含义的字符串(也称为command )。
    • data是消息的任何参数数据; 通常是地图,列表或数组。 参数可以是要处理的数据和/或其他与之交互的参与者。
    • subjectMatches()检查消息主题是否匹配字符串或正则表达式。
    μJavaActors包的默认消息类是DefaultMessage
  • ActorManager是演员的经理。 它负责分配线程(从而分配处理器)给参与者以处理消息。 ActorManager具有以下关键行为或特征:
    • createActor()创建一个actor并将其与此管理器关联。
    • startActor()启动一个actor。
    • detachActor()停止一个actor并将其与该管理器解除关联。
    • send()/broadcast()将消息发送给演员,一组演员,类别的任何演员或所有演员。
    在大多数程序中,只有一个ActorManager ,但是如果要管理多个线程和/或Actor池,则可以使用多个。 此接口的默认实现是DefaultActorManager
  • Actor是一次处理一个消息的执行单元。 Actor具有以下关键行为或特征:
    • 每个actor都有一个name ,每个ActorManagername必须唯一。
    • 每个演员都属于一个category ; 类别是向一组参与者中的一个成员发送消息的一种方式。 演员一次只能属于一个类别。
    • 每当ActorManager可以提供一个线程来执行actor时, ActorManager调用receive() 。 仅当参与者的消息存在时才调用它。 为了最有效,演员应该快速处理消息,并且不要输入长时间的等待(例如人工输入)。
    • willReceive()允许willReceive()过滤潜在的消息主题。
    • peek()允许演员和其他人查看是否有待处理消息,可能是针对选定主题的。
    • remove()允许参与者和其他参与者删除或取消任何尚未处理的消息。
    • getMessageCount()允许getMessageCount()和其他getMessageCount()获取待处理消息的数量。
    • getMaxMessageCount()允许getMaxMessageCount()限制支持的待处理消息数量。 此方法可用于防止发送失控。
    大多数程序都有许多演员,通常是不同类型的演员。 Actor可以在程序开始时创建,也可以在程序执行时创建(并销毁)。 本文中的actor包包括一个名为AbstractActor的抽象类,actor实现基于该抽象类。

图1显示了参与者之间的关系。 每个演员可以向其他演员发送消息。 消息保存在消息队列中(也称为邮箱 ;概念上每个ActorManager一个邮箱 ),当ActorManager看到有可用的线程来处理消息时,该消息将从队列中删除并传递给在下面运行的actor处理该消息的线程。

图1.参与者之间的关系
演员通过线程传递的消息发送给演员

使用μJavaActors并行执行

戏就是这个!

现在您可以开始使用μJavaActors进行并行执行了。 您将从创建一组参与者开始。 这些演员很简单,因为他们所做的只是延迟一小段时间,然后将消息发送给其他演员。 这样做的结果是造成大量的消息,随着时间的流逝而安静下来,并最终停止。 在下面的演示中,您将首先看到如何创建角色,然后逐步分配它们以处理消息。

有两种消息类型:

  • initializationinit )使参与者进行初始化。 每个演员仅发送一次。
  • repeat会导致参与者发送N-1条消息,其中N是传入的消息参数。

清单1中的TestActor实现了从AbstractActor继承的抽象方法。 activatedeactivate方法将其生命周期告知参与者。 在此示例中,无需执行其他任何操作。 首次创建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)   
[main         ] - main waiting: 8...
[actor7       ] - TestActor:actor2 repeat(0)   
[actor13      ] - TestActor:actor1 repeat(0)   
[main         ] - main waiting: 7...
[actor2       ] - TestActor:actor2 repeat(2)   
[main         ] - main waiting: 6...
[main         ] - main waiting: 5...
[actor18      ] - TestActor:actor1 repeat(1)   
[main         ] - main waiting: 4...
[actor15      ] - TestActor:actor2 repeat(0)   
[actor16      ] - TestActor:actor1 repeat(1)   
[main         ] - main waiting: 3...
[main         ] - main waiting: 2...
[main         ] - main waiting: 1...
[actor4       ] - TestActor:actor1 repeat(0)   
[actor6       ] - TestActor:actor2 repeat(0)

模拟屏幕截图

完全掌握actor系统在先前跟踪中的行为是具有挑战性的,部分原因是跟踪格式的信息不足。 通过执行类似的actor模拟获得的快照图像可以使您以图形格式查看相同的信息。 每个图像显示固定时间段后的模拟。 以下视频说明了代码示例和屏幕截图未捕获的一些Java actor进程。 您可以在下面或在YouTube上内联观看视频,该视频提供了交互式笔录功能,可让您在观看时选择特定的拍号。 只需点击

交互式笔录图标
视频屏幕下方的图标将其启用。

此处查看成绩单。

图2显示了运行任何模拟之前的模拟用户界面。 注意右侧显示的模拟菜单的内容。

图2.任何模拟之前的Actor模拟器
执行前的Actor模拟器

屏幕顶部显示模拟菜单,其中可能包含多种变体。 除非另有说明,否则以下模拟显示在跟踪输出和以下屏幕截图中:

  • 倒数模拟(0:15)创建的actor将值倒数为零并发送更多请求。
  • 生产者/消费者模拟(2:40)创建了经典的生产者/消费者并发问题的变体。
  • Map / Reduce模拟(5:28)创建1000个整数平方和的并行执行。
  • 病毒扫描模拟(6:45)在磁盘目录树中扫描“ .txt”文件(以限制扫描的数量),并检测可疑的内容模式。 以下屏幕快照中未显示此非CPU绑定的模拟,但这是视频演示的一部分。
  • 所有模拟仅在视频演示中同时运行(8:18)。

视频格式显示了所有这些仿真顺序运行的过程,它们之间有短暂的暂停。

除了“开始”和“停止”之外,图2中的屏幕快照还显示以下控件和设置。 (请注意,停止不会停止线程,因此在停止后可能会发生某些操作。)

  • 重新随机分布actor圈子中的actor(默认顺序是创建顺序)。 通过重新定位角色,可以使在紧密分组的角色之间的消息更易于查看。 它还可以为演员分配新的颜色。
  • 添加任务和删除任务向/从启动池添加或删除任务(线程)。 删除任务只会删除添加的(不是原始)任务。
  • 最大步长 (在使用值的对2中)限制了模拟的时间,并且仅在模拟开始之前才有效。 步骤大约需要一秒钟。
  • 将角色显示为透明可以更容易地看到相邻角色之间的消息。 不透明的演员通常更容易被看到。 在运行模拟时可以更改此设置。
  • 仅在模拟开始之前,使用微调器的线程数才有效。 使用更多线程,许多仿真运行速度更快。

控件下方的显示块显示当前线程的使用情况(过去一秒的平均值)。 大的中心区域显示了模拟。 底部显示模拟历史记录。 右侧区域显示了完整的模拟轨迹。 运行时,模拟框架的配置如下:

  • 在控制区域中,仪表大约每秒更新一次:
    • 每秒接受邮件的数量。
    • 每秒消息完成数。
    • 邮件接受与每秒完成量的比较。
      如果右侧显示活动,则表示到达的消息多于正在处理的消息; 最终,消息缓冲区将溢出。 如果活动显示在左侧,则正在处理的消息多于到达的消息; 最终,系统将进入空闲状态。 平衡的系统在很长的时间间隔内显示零或仅绿色水平。
  • 中心区域上方是绿色条形网格; 每个条代表一个线程(如外圈)。 完全绿色的条表示线程已被充分利用,而完全黄色的条表示线程已完全空闲。
  • 在中心区域,正方形的外圈代表螺纹(在这些模拟中为10,在上一条曲线中为25)。 绿色线程连接到参与者,以执行收到的消息; 中心点的颜色表示演员类型。 正方形附近的数字是当前分配给该线程的演员编号(从左侧的0到360度按顺时针顺序排列)。 黄线空闲。
  • 圆圈的内圈代表演员。 颜色表示类型(在第一个示例中只有一种类型)。 如果参与者正在忙于处理消息,则会以较暗的阴影显示该消息(如果使用了不透明的参与者,则会更加明显)。 圆圈(演员)之间的线代表消息。 任何亮红色的线都是在给定的刷新周期中发送的新消息(模拟每秒刷新10次); 其他颜色是缓冲的消息(过去发送但尚未处理)。 缓冲线在接收端有一个小圆圈。 随着缓冲消息数量的增加,圆圈的大小也会增加。
  • 最右端是输出轨迹的显示。 此痕迹与前面讨论的痕迹相似但更详细。
  • 图片底部是一组较小的圆圈; 每个都是过去时间隔显示的按比例缩小的主圆圈显示。 这提供了一种查看消息随时间变化趋势的简便方法。 如果您查看此历史记录,您将看到消息积压Swift建立,然后逐渐减少。

图3显示了执行大约10秒钟后的仿真 。 请注意,大量待定消息已Swift建立。 有34个参与者,只有10个线程,因此某些参与者将必定是空闲的。 此时,所有线程都在忙于处理消息。

图3.开始附近的倒数模拟(0:15)
倒数模拟执行10秒

图4是执行大约30秒后的仿真。 待处理消息的数量已大大减少。 由于较低的消息到达速率,因此只有某些线程正在完全忙于处理消息。

图4.倒计时中间的模拟
倒计时模拟执行30秒

图5是执行大约90秒后的仿真。 现在,所有待处理的消息都已处理,因此所有线程都处于空闲状态。

图5.倒数模拟完成后
倒数模拟执行90秒

生产者/消费者系统中的参与者

接下来,让我们看一下生产者/消费者模式中参与者的演示。 生产者/消费者是多处理器系统中最常见的同步模式之一。 在随后的μJavaActors演示中,生产者参与者向消费者参与者生成请求以创建各种项目。 消费者将创建这些项目(需要一些时间),然后将完成消息发送回发出请求的生产者。

图6显示了执行大约30秒后的视频仿真 。 请注意,两种演员类型按颜色区分。 首先在屏幕的右下方显示制作人演员。 生产者在运行时创建消费者,因此它们将显示在下一个位置。 工作量随着时间的推移逐渐减少,并且线程大多处于繁忙状态。 请注意,生产者完成任务的速度如此之快,以至于他们很少活跃。

图6.开始时的生产者/消费者模拟(2:40)
在执行30秒时显示生产者/消费者模拟

图7显示了执行大约115秒后的仿真,接近程序的完成。 新请求和待处理消息的数量已大大减少。 在视频演示中,您可能会注意到一些演员短暂地显示为未填充的圆圈。 这些是处理发送给自己的消息的参与者。

图7.生产者/消费者模拟接近尾声
在115秒执行时显示的生产者/消费者模拟

监制

清单7显示了演示中的生产者actor的代码。 此处处理“ produceN ”消息。 它被转换为produce1产生的“ produce1 ”消息。 预期的响应将记录为未决答复计数,以供以后验证。

清单7.制片人演员
public class ProducerActor extends AbstractActor {
  Map<String , Integer> expected = new ConcurrentHashMap<String
        , Integer>();

  @Override
  protected void loopBody(Message m) {
    String subject = m.getSubject();
    if ("produceN".equals(subject)) {
      Object[] input = (Object[]) m.getData();
      int count = (Integer) input[0];
      if (count > 0) {
        DefaultActorTest.sleeper(1); // this takes some time
        String type = (String) input[1];
        // request the consumers to consume work (i.e., produce)
        Integer mcount = expected.get(type);
        if (mcount == null) {
          mcount = new Integer(0);
        }
        mcount += count;
        expected.put(type, mcount);

        DefaultMessage dm = new DefaultMessage("produce1", 
          new Object[] { count, type });
        getManager().send(dm, this, this);
      }

在清单8中,处理了“ produce1 ”消息。 如果剩余计数大于零,则将其转换为“ construct ”消息并发送给使用者。 注意,该逻辑可以作为对计数值的for循环来完成,而不是重新发送“ produce1 ”消息。 重新发送消息通常会在线程上产生更好的负载,尤其是在循环主体花费大量时间的情况下。

清单8.处理生产者请求
} else if ("produce1".equals(subject)) {
      Object[] input = (Object[]) m.getData();
      int count = (Integer) input[0];
      if (count > 0) {
        sleep(100); // take a little time
        String type = (String) input[1];
        m = new DefaultMessage("construct", type);
        getManager().send(m, this, getConsumerCategory());

        m = new DefaultMessage("produce1", new Object[] { count - 1, type });
        getManager().send(m, this, this);
      }

在清单9中,处理了“ constructionComplete ”消息(由消费者发送)。 减少待处理的答复计数。 如果所有工作均正常进行,则在模拟完成时,所有参与者和类型值的计数均为零。

清单9. constructionComplete
} else if ("constructionComplete".equals(subject)) {
      String type = (String) m.getData();
      Integer mcount = expected.get(type);
      if (mcount != null) {
        mcount--;
        expected.put(type, mcount);
      }

清单10中处理了“ init ”消息。生产者创建了一些消费者actor,然后向其自身发送了几个produceN请求。

清单10.初始化
} else if ("init".equals(subject)) {
      // create some consumers; 1 to 3 x consumers per producer
      for (int i = 0; i < DefaultActorTest.nextInt(3) + 1; i++) {
        Actor a = getManager().createAndStartActor(ConsumerActor.class,
            String.format("%s_consumer%02d", getName(), i));
        a.setCategory(getConsumerCategory());
        if (actorTest != null) {
          actorTest.getTestActors().put(a.getName(), a);
        }
      }
      // request myself create some work items
      for (int i = 0; i < DefaultActorTest.nextInt(10) + 1; i++) {
        m = new DefaultMessage("produceN", new Object[] 
             { DefaultActorTest.nextInt(10) + 1,
               DefaultActorTest.getItemTypes()[
                  DefaultActorTest.nextInt(DefaultActorTest.getItemTypes().length)] });
        getManager().send(m, this, this);
      }

清单11处理无效消息:

清单11.处理无效消息
} else {
      System.out.printf("ProducerActor:%s loopBody unknown subject: %s%n", 
         getName(), subject);
    }
  }

  protected String getConsumerCategory() {
    return getName() + "_consumer";
  }
}

消费者演员

消费者参与者很简单。 它处理“ construct ”消息,并将回复消息发送回请求者。 清单12中显示了消费者参与者的代码:

清单12.消费者参与者
public class ConsumerActor extends AbstractActor {

  @Override
  protected void loopBody(Message m) {
    String subject = m.getSubject();
    if ("construct".equals(subject)) {
      String type = (String) m.getData();
      delay(type); // takes ~ 1 to N seconds

      DefaultMessage dm = new 
         DefaultMessage("constructionComplete", type);
      getManager().send(dm, this, m.getSource());
    } else if ("init".equals(subject)) {
      // nothing to do
    } else {
      System.out.printf("ConsumerActor:%s loopBody unknown subject: %s%n", 
        getName(), subject);
    }
  }

清单13中处理的生产延迟是基于所构造物料的类型的。 从痕迹中,您可能会记得支持的项目类型是widgetframitfrizzlegothcasplat 。 每种类型的构建时间不同。

清单13.生产延迟
protected void delay(String type) {
    int delay = 1;
    for (int i = 0; i < DefaultActorTest.getItemTypes().length; i++) {
      if (DefaultActorTest.getItemTypes()[i].equals(type)) {
        break;
      }
      delay++;
    }
    DefaultActorTest.sleeper(DefaultActorTest.nextInt(delay) + 1);
  }
}

生产者/消费者模式中的参与者

Producer / Consumer演示表明,创建actor实现非常简单。 典型的actor解码接收到的消息并对其进行处理,就像在case语句中一样。 在此示例中,实际处理很简单,只是时间延迟。 在实际的应用程序中,它会更复杂,但没有比使用标准Java同步技术的实现更复杂。 通常,它会简单得多。

该演示中还有一点要注意的是,复杂的算法(尤其是重复的算法)可以分解为离散的(通常可重复使用的)步骤。 可以为每个步骤分配一个不同的主题名称,这使得每个主题的情况非常简单。 在消息参数中携带状态时(例如先前演示的倒数值),许多参与者可能会变成无状态。 这样的程序非常易于定义和扩展(添加了更多的actor以匹配更多的线程),但是可以安全地在多线程环境中运行; 这类似于在函数式编程中使用不可变值。

演员的更多模式

Producer / Consumer演示中的actor被硬编码为特定目的,但这并不是编码actor时的唯一选择。 在本节中,您将学习在更通用的模式中使用actor,首先是对“ 四人帮”模式的改编。

清单14中的actor实现了大多数Java开发人员都应该熟悉的Command模式的变体。 在这里, CommandActor支持两种消息,即“ execute ”和“ executeStatic

清单14. CommandActor
public class CommandActor extends AbstractActor {

  @Override
  protected void loopBody(Message m) {
    String subject = m.getSubject();
    if ("execute".equals(subject)) {
      excuteMethod(m, false);
    } else if ("executeStatic".equals(subject)) {
      excuteMethod(m, true);
    } else if ("init".equals(subject)) {
      // nothing to do
    } else {
      System.out.printf("CommandActor:%s loopBody unknown subject: %s",
          getName(), subject);
    }
  }

清单15中的executeMethod方法加载一个参数化的类,在该类或该类的实例上调用一个方法,并返回该方法的结果或发生的任何异常。 您可以看到如何使用这个简单的actor来运行具有适当执行方法的类路径上可用的任何服务类。 id参数由客户端发送,因此可以将响应与创建响应的请求相关联。 通常,回信的顺序与发出的顺序不同。

清单15.执行参数化方法
private void excuteMethod(Message m, boolean fstatic) {
    Object res = null;
    Object id = null;
    try {
      Object[] params = (Object[]) m.getData();
      id = params[0];
      String className = (String) params[1];
      params = params.length > 2 ? (Object[]) params[2] : null;
      Class<?> clazz = Class.forName(className);
      Method method = clazz.getMethod(fstatic ? "executeStatic"
          : "execute", new Class[] { Object.class });
      if (Modifier.isStatic(method.getModifiers()) == fstatic) {
        Object target = fstatic ? null : clazz.newInstance();
        res = method.invoke(target, params);
      }
    } catch (Exception e) {
      res = e;
    }

    DefaultMessage dm = new DefaultMessage("executeComplete", new Object[] {
        id, res });
    getManager().send(dm, this, m.getSource());
  }
}

事件侦听器模式中的参与者

清单16中的DelegatingActor基于熟悉的Java Event Listener(或Callback)模式实现了类似的通用方法。 它将每个到达的消息映射到每个注册的侦听器上的onMessage回调,直到一个回调消耗(即处理)该事件。 这种委托方法可以显着减少参与者系统与其消息处理器之间的耦合。

清单16. DelegatingActor
public class DelegatingActor extends AbstractActor {
  private List<MessageListener> listeners = new LinkedList<MessageListener>();

  public void addMessageListener(MessageListener ml) {
    if (!listeners.contains(ml)) {
      listeners.add(ml);
    }
  }

  public void removeMessageListener(MessageListener ml) {
    listeners.remove(ml);
  }

  protected void fireMessageListeners(MessageEvent me) {
    for (MessageListener ml : listeners) {
      if (me.isConsumed()) {
        break;
      }
      ml.onMessage(me);
    }
  }

  @Override
  protected void loopBody(Message m) {
    fireMessageListeners(new MessageEvent(this, m));
  }
}

清单17中所示的DelegatingActor类取决于MessageEventMessageListener类:

清单17. DelegatingActor
/** Defines a message arrival event. */
public static class MessageEvent extends EventObject {
  private Message message;

  public Message getMessage() {
    return message;
  }

  public void setMessage(Message message) {
    this.message = message;
  }

  private boolean consumed;

  public boolean isConsumed() {
    return consumed;
  }

  public void setConsumed(boolean consumed) {
    this.consumed = consumed;
  }

  public MessageEvent(Object source, Message msg) {
    super(source);
    setMessage(msg);
  }
}

/** Defines the message arrival call back. */
public interface MessageListener {
  void onMessage(MessageEvent me);
}

清单18显示了DelegatingActor使用示例:

清单18. DelegatingActor的示例用法
public static void addDelegate(DelegatingActor da) {
  MessageListener ml = new Echo("Hello world!");
  da.addMessageListener(ml);
}
	
	
public class Echo implements MessageListener {
  protected String message;

  public Echo(String message) {
    this.message = message;
  }

  @Override
  public void onMessage(MessageEvent me) {
    if ("echo".equals(me.getMessage().getSubject())) {
      System.out.printf("%s says \"%s\".%n", 
         me.getMessage().getSource(), message);
      me.setConsumed(true);
    }
  }
}

Map / Reduce模式中的Actor

清单14至18中的示例参与者很简单明了,因为消息仅在一个方向上发送。 如果该行为需要反馈(例如,在处理完所有先前的消息之前无法继续进行处理),事情可能会变得更加复杂。 例如,考虑一个Map / Reduce实现,在该实现中,在map阶段完成之前,无法启动reduce阶段。

Map / Reduce用于对处理大量数据的程序进行并行处理。 在下面的示例中, map函数获取大量项目,将其划分为多个分区,然后发送一条消息以映射每个分区。 我选择增加每个映射请求的消息计数,并让分区的映射处理器发送降低计数的回复。 当计数达到零时,所有映射完成,并且reduce阶段可以开始。 同样, reduce阶段还会对列表进行分区(再次出于并行性考虑),并发送消息以reduce分区。 像在map阶段一样, reduce还统计其消息,以便可以检测到缩减的完成。 要处理的值列表和计数在每个消息中作为参数传递。

在此示例中,我使用了具有多个主题的单个演员类型。 您也可以使用多种演员类型,并且每个演员的主题更少(最多一个)。

图8是执行约20秒后的Map / Reduce模拟。 这是处理的繁忙阶段,因此线程被处理消息占用。

图8.在起点附近映射/缩小(5:28)
在执行20秒后进行Map / Reduce模拟

使用MapReduceer映射和缩小

请注意,此实现是可插入的; 它可以运行MapReduceer接口的任何实现,如清单19所示。

清单19. MapReduceer
public interface MapReduceer {
  /**
   * Map (in place) the elements of an array.
   * 
   * @param values elements to map
   * @param start start position in values
   * @param end end position in values
   */
  void map(Object[] values, int start, int end);

  /**
   * Reduce the elements of an array.
   * 
   * @param values elements to reduce
   * @param start start position in values
   * @param end end position in values
   * @param target place to set reduced value
   * @param posn position in target to place the value
   */
  void reduce(Object[] values, int start, int end, Object[] target, int posn);
}

例如,您可以使用MapReduceer计算一组整数的平方和,如清单20所示:

清单20. MapReduceer计算
public class SumOfSquaresReducer implements MapReduceer {
  @Override
  public void map(Object[] values, int start, int end) {
    for (int i = start; i <= end; i++) {
      values[i] = ((BigInteger) values[i]).multiply((BigInteger) values[i]);
      sleep(200); // fake taking time
    }
  }

  @Override
  public void reduce(Object[] values, int start, int end, Object[] target, int posn) {
    BigInteger res = new BigInteger("0");
    for (int i = start; i <= end; i++) {
      res = res.add((BigInteger) values[i]);
      sleep(100); // fake taking time
    }
    target[posn] = res;
  }
}

MapReduceActor

Map / Reduce参与者分为许多主题,每个主题都有一个简单的任务。 您将在下面的代码示例中查看每个示例。 我也鼓励您在视频演示中查看Map / Reduce操作 ; 观看仿真,然后研究代码示例,将使您清楚地了解如何使用actor实现Map / Reduce。 (请注意,以下清单中的主题顺序可以通过多种方式分解;我设计了带有许多发送的示例代码,以使视频演示更加有趣。)

清单21中所示的mapReduce主题通过对输入数组进行分区来启动Map / Reduce,它通过发送createPartition消息来完成。 MapReduceParameters实例中提供了map和reduce参数,可以根据需要对其进行克隆和修改,然后继续进行传递。 请注意,操作不需要时间延迟; 我添加了它们,以确保可以在用户界面中看到模拟。

清单21. mapReduce
@Override
  protected void loopBody(Message m) {
    ActorManager manager = getManager();
    String subject = m.getSubject();
    if ("mapReduce".equals(subject)) {
      try {
        MapReduceParameters p = (MapReduceParameters) m.getData();
        int index = 0;
        int count = (p.end - p.start + 1 + partitionSize - 1) / partitionSize;
        sleep(1000);
        // split up into partition size chunks
        while (p.end - p.start + 1 >= partitionSize) {
          MapReduceParameters xp = new MapReduceParameters(p);
          xp.end = xp.start + partitionSize - 1;
          DefaultMessage lm = new DefaultMessage("createPartition", 
            new Object[] { xp, index, count });
          manager.send(lm, this, getCategory());
          p.start += partitionSize;
          index++;
        }
        if (p.end - p.start + 1 > 0) {
          DefaultMessage lm = new DefaultMessage("createPartition", 
            new Object[] { p, index, count });
          manager.send(lm, this, getCategory());
        }
      } catch (Exception e) {
        triageException("mapFailed", m, e);
      }
}

createPartition主题创建更多的actor并将请求转发给worker,如清单22所示。请注意, createMapReduceActor方法在将要创建的actor数量上具有上限(当前为25)。

清单22. createPartition
} else if ("createPartition".equals(subject)) {
      try {
        Object[] oa = (Object[]) m.getData();
        MapReduceParameters p = (MapReduceParameters) oa[0];
        int index = (Integer) oa[1];
        int count = (Integer) oa[2];
        sleep(500);
        createMapReduceActor(this);
        DefaultMessage lm = new DefaultMessage("mapWorker", 
          new Object[] { p, index, count });
        manager.send(lm, this, getCategory());
      } catch (Exception e) {
        triageException("createPartitionFailed", m, e);
      }
}

清单23中的mapWorker主题通过提供的MapReducer在其分区上调用map操作,然后回复map分区已完成:

清单23. mapWorker
} else if ("mapWorker".equals(subject)) {
      try {
        Object[] oa = (Object[]) m.getData();
        MapReduceParameters p = (MapReduceParameters) oa[0];
        int index = (Integer) oa[1];
        int count = (Integer) oa[2];
        sleep(100);
        p.mr.map(p.values, p.start, p.end);
        DefaultMessage rm = new DefaultMessage("mapResponse", 
          new Object[] { p, index, count });
        manager.send(rm, this, getCategoryName());
      } catch (Exception e) {
        triageException("mapWorkerFailed", m, e);
      }
}

然后,清单24中的mapResponse主题完成了MapReduceParameters实例(其中包含计数)并开始减少过程:

清单24. mapResponse
} else if ("mapResponse".equals(subject)) {
      try {
        Object[] oa = (Object[]) m.getData();
        MapReduceParameters p = (MapReduceParameters) oa[0];
        int index = (Integer) oa[1];
        int count = (Integer) oa[2];
        sleep(100);
        p.complete();
        DefaultMessage rm = new DefaultMessage("reduce", 
          new Object[] { p, index, count });
        manager.send(rm, this, getCategoryName());
      } catch (Exception e) {
        triageException("mapResponseFailed", m, e);
      }
}

接下来, reduce消息将请求转发给工作程序,如清单25所示:

清单25. reduce
} else if ("reduce".equals(subject)) {
      try {
        MapReduceParameters p = null;
        int index = 0, count = 0;
        Object o = m.getData();
        if (o instanceof MapReduceParameters) {
          p = (MapReduceParameters) o;
        } else {
          Object[] oa = (Object[]) o;
          p = (MapReduceParameters) oa[0];
          index = (Integer) oa[1];
          count = (Integer) oa[2];
        }
        sleep(100);
        if (p.end - p.start + 1 > 0) {
          createMapReduceActor(this);
          MapReduceParameters xp = new MapReduceParameters(p);
          DefaultMessage lm = new DefaultMessage("reduceWorker", 
            new Object[] { xp, index, count });
          manager.send(lm, this, getCategory());
        }
      } catch (Exception e) {
        triageException("reduceFailed", m, e);
      }
}

清单26中的reduceWorker主题通过提供的MapReducer在其分区上调用reduce操作,并答复说还原已完成。 如果所有还原操作均已完成,则答复已完成“映射/还原”操作。

清单26. reduceWorker
} else if ("reduceWorker".equals(subject)) {
      try {
        Object[] oa = (Object[]) m.getData();
        MapReduceParameters p = (MapReduceParameters) oa[0];
        int index = (Integer) oa[1];
        int count = (Integer) oa[2];
        sleep(100);
        if (index >= 0) {
          p.mr.reduce(p.values, p.start, p.end, p.target, index);
          DefaultMessage rm = new DefaultMessage("reduceResponse", 
            new Object[] { p, index, count });
          manager.send(rm, this, getCategory());
        } else {
          Object[] res = new Object[1];
          p.mr.reduce(p.target, 0, count - 1, res, 0);
          DefaultMessage rm = new DefaultMessage("done", 
            new Object[] { p, res[0] });
          manager.send(rm, this, getCategory());
        }
      } catch (Exception e) {
        triageException("reduceWorkerFailed", m, e);
      }
}

接下来,清单27中的reduceResponse主题完成了分区并测试了所有分区的完成并发出信号:

清单27. reduceResponse
} else if ("reduceResponse".equals(subject)) {
      try {
        Object[] oa = (Object[]) m.getData();
        MapReduceParameters p = (MapReduceParameters) oa[0];
        int index = (Integer) oa[1];
        int count = (Integer) oa[2];
        sleep(100);
        p.complete();
        if (p.isSetComplete()) {
          if (count > 0) {
            createMapReduceActor(this);
            MapReduceParameters xp = new MapReduceParameters(p);
            DefaultMessage lm = new DefaultMessage("reduceWorker", 
              new Object[] { xp, -1, count });
            manager.send(lm, this, getCategory());
          }
        }
      } catch (Exception e) {
        triageException("mapResponseFailed", m, e);
      }
}

最后,清单28中的done主题报告了结果:

清单28.完成
} else if ("done".equals(subject)) {
      try {
        Object[] oa = (Object[]) m.getData();
        MapReduceParameters p = (MapReduceParameters) oa[0];
        Object res = oa[1];
        sleep(100);
        System.out.printf("**** mapReduce done with result %s", res);
      } catch (Exception e) {
        triageException("mapResponseFailed", m, e);
      }
}

继续循环, init主题启动另一个Map / Reduce流程,如清单29所示。每个Map / Reduce被赋予一个不同的“集合”名称,以便可以同时运行多个Map / Reduce。

清单29.初始化另一个Map / Reduce
} else if ("init".equals(subject)) {
      try {
        Object[] params = (Object[]) m.getData();
        if (params != null) {
          Object[] values = (Object[]) params[0];
          Object[] targets = (Object[]) params[1];
          Class clazz = (Class) params[2];
          MapReduceer mr = (MapReduceer) clazz.newInstance();
          sleep(2 * 1000);
          MapReduceParameters p = new MapReduceParameters("mrSet_" + setCount++, 
            values, targets, mr, this);
          DefaultMessage rm = new DefaultMessage("mapReduce", p);
          manager.send(rm, this, getCategoryName());
        }
      } catch (Exception e) {
        triageException("initFailed", m, e);
      }
    } else {
      System.out.printf("**** MapReduceActor:%s loopBody unexpected subject: %s", 
        getName(), subject);
    }
  }
}

映射/缩小主

清单30中的MapReduceActor实现创建一些数据值并对该数据运行Map / Reduce。 它将分区大小设置为10。

清单30. Map / Reduce主对象
BigInteger[] values = new BigInteger[1000];
for (int i = 0; i < values.length; i++) {
  values[i] = new BigInteger(Long.toString((long)rand.nextInt(values.length)));
}
BigInteger[] targets = new BigInteger[Math.max(1, values.length / 10)];

// start at least 5 actors
DefaultActorManager am = new DefaultActorManager();
MapReduceActor.createMapReduceActor(am, 10);
MapReduceActor.createMapReduceActor(am, 10);
MapReduceActor.createMapReduceActor(am, 10);
MapReduceActor.createMapReduceActor(am, 10);
MapReduceActor.createMapReduceActor(am, 10);
        
DefaultMessage dm = new DefaultMessage("init", new Object[] 
    { values, targets, SumOfSquaresReducer.class });
am.send(dm, null, MapReduceActor.getCategoryName());

Map / Reduce是最常见的分而治之设计模式之一。 从基本的功能编程算法一直到大规模并行处理(Google用来建立其网络搜索引擎索引的类型),都使用它。 μJavaActors库可以以这种直接的方式实现这种高级模式,这表明了它的功能以及潜在的用途。

在μJavaActors库中

经理到演员:别叫我; 我会打电话给你。

您已经了解了如何使用actor来重新调整一些常见的面向对象模式的用途。 现在考虑μJavaActors系统的实现细节,即AbstractActorDefaultActorManager类。 我将只讨论每个类的关键方法。 您可以查看μJavaActors 源代码以获取更多实现细节。

抽象演员

每个角色都知道管理它的ActorManager 。 参与者使用管理器来帮助它向其他参与者发送消息。

在清单31中, receive方法有条件地处理消息。 如果testMessage方法返回null ,则不会消耗任何消息。 否则,将从loopBody的消息队列中删除该消息,并通过调用loopBody方法loopBody处理。 每个具体的actor子类都必须提供此方法。 无论哪种情况, awaitMessage都可以通过调用管理器的awaitMessage方法来等待更多消息。

清单31. AbstractActor实现DefaultActorManager
public abstract class AbstractActor implements Actor {
  protected DefaultActorManager manager;

  @Override
  public boolean receive() {
    Message m = testMessage();
    boolean res = m != null;
    if (res) {
      remove(m);
      try {
        loopBody(m);
      } catch (Exception e) {
        System.out.printf("loop exception: %s%n", e);
      }
    }
    manager.awaitMessage(this);
    return res;
  }

  abstract protected void loopBody(Message m);

每个willReceive都可以实现willReceive方法来控制将接受哪些消息主题(这意味着它将被放置在消息列表中)。 默认情况下,接受所有主题为非空的消息。 每个testMessage也可以实现testMessage方法,以检查是否有消息可以处理(即,消息列表中是否存在); 默认情况下,此监督是使用peekNext方法实现的。

清单32. willReceive(),testMessage()和peekNext()
@Override
  public boolean willReceive(String subject) {
    return !isEmpty(subject); 
  }

  protected Message testMessage() {
    return getMatch(null, false);
  }

  protected Message getMatch(String subject, boolean isRegExpr) {
    Message res = null;
    synchronized (messages) {
      res = peekNext(subject, isRegExpr);
    }
    return res;
  }

讯息容量

参与者可以具有无限或有限的消息容量。 通常,有限的容量会更好,因为它可以帮助检测失控的消息发件人。 任何客户端(但通常是ActorManager )都可以将未ActorManager消息添加到actor。 请注意,对messages列表的所有访问都是同步的。

清单33.消息处理
public static final int DEFAULT_MAX_MESSAGES = 100;
  protected List<DefaultMessage> messages = new LinkedList<DefaultMessage>();

  @Override
  public int getMessageCount() {
    synchronized (messages) {
      return messages.size();
    }
  }

  @Override
  public int getMaxMessageCount() {
    return DEFAULT_MAX_MESSAGES;
  }

  public void addMessage(Message message) {
    synchronized (messages) {
      if (messages.size() < getMaxMessageCount()) {
        messages.add(message);
      } else {
        throw new IllegalStateException("too many messages, cannot add");
      }
    }
  }

  @Override
  public boolean remove(Message message) {
    synchronized (messages) {
      return messages.remove(message);
    }
  }

讯息匹配

客户端(尤其是参与者本身)可以检查参与者是否有未决消息。 这可用于处理发送顺序以外的消息,或为某些主题赋予优先级。 通过测试消息主题是否与字符串值相等或将正则表达式与参数值进行匹配来完成消息匹配。 null主题匹配任何消息。 同样,请注意,对消息列表的所有访问都是同步的。

清单34. peekNext()
@Override
  public Message peekNext() {
    return peekNext(null);
  }

  @Override
  public Message peekNext(String subject) {
    return peekNext(subject, false);
  }

  @Override
  public Message peekNext(String subject, boolean isRegExpr) {
    long now = new Date().getTime();
    Message res = null;
    Pattern p = subject != null ? (isRegExpr ? Pattern.compile(subject) : null) : null;
    synchronized (messages) {
      for (DefaultMessage m : messages) {
        if (m.getDelayUntil() <= now) {
          boolean match = subject == null || 
            (isRegExpr ? m.subjectMatches(p) : m.subjectMatches(subject));
          if (match) {
            res = m;
            break;
          }
        }
      }
    }
    return res;
  }

生命周期方法

每个参与者都有生命周期方法 。 每次与特定ActorManager关联时,一次调用activatedeactivate方法。 每个与特定ActorManager关联也都会调用一次run方法,并且通常通过自动发送启动消息来启动该actor。 run消息开始消息处理。

清单35.生命周期方法
@Override
  public void activate() {
    // defaults to no action
  }

  @Override
  public void deactivate() {
    // defaults to no action
  }

  /** Do startup processing. */
  protected abstract void runBody();

  @Override
  public void run() {
    runBody();
    ((DefaultActorManager) getManager()).awaitMessage(this);
  }
}

DefaultActorManager

以下字段包含演员管理员的状态:

  • actors拥有所有向经理注册的演员。
  • runnables保留所有尚未调用run方法创建的参与者。
  • waiters拿着所有演员等待消息。
  • threads保存由管理器启动的所有线程。

请注意, LinkedHashMap的使用非常关键(尤其是在服务员列表中)。 否则,某些参与者可能会饿死。

清单36. DefaultActorManager的类和状态
public class DefaultActorManager implements ActorManager {

  public static final int DEFAULT_ACTOR_THREAD_COUNT = 25;

  protected static DefaultActorManager instance;
  public static DefaultActorManager getDefaultInstance() {
    if (instance == null) {
      instance = new DefaultActorManager();
    }
    return instance;
  }

  protected Map<String , AbstractActor> actors = 
    new LinkedHashMap<String , AbstractActor>();

  protected Map<String , AbstractActor> runnables = 
    new LinkedHashMap<String , AbstractActor>();

  protected Map<String , AbstractActor> waiters = 
    new LinkedHashMap<String , AbstractActor>();

  protected List<Thread> threads = new LinkedList<Thread>();

detachActor方法破坏了actor及其管理者之间的关联:

清单37. Actor的终止
@Override
  public void detachActor(Actor actor) {
    synchronized (actors) {
      actor.deactivate();
      ((AbstractActor)actor).setManager(null);
      String name = actor.getName();
      actors.remove(name);
      runnables.remove(name);
      waiters.remove(name);
    }
  }

发送方法

send系列方法将消息发送给一个或多个参与者。 首先检查每个消息,以查看参与者是否会接受它。 消息排队后,将使用notify唤醒线程来处理消息。 When sending to a category, only one actor in the category — the one with the fewest current messages — is actually sent the message. The awaitMessage method simply queues the actors on the waiters list.

Listing 38. DefaultActorManager class processing a send
@Override
  public int send(Message message, Actor from, Actor to) {
    int count = 0;
    AbstractActor aa = (AbstractActor) to;
    if (aa != null) {
      if (aa.willReceive(message.getSubject())) {
        DefaultMessage xmessage = (DefaultMessage) 
           ((DefaultMessage) message).assignSender(from);
        aa.addMessage(xmessage);
        count++;
        synchronized (actors) {
          actors.notifyAll();
        }
      }
    }
    return count;
  }

  @Override
  public int send(Message message, Actor from, Actor[] to) {
    int count = 0;
    for (Actor a : to) {
      count += send(message, from, a);
    }
    return count;
  }

  @Override
  public int send(Message message, Actor from, Collection<Actor> to) {
    int count = 0;
    for (Actor a : to) {
      count += send(message, from, a);
    }
    return count;
  }

  @Override
  public int send(Message message, Actor from, String category) {
    int count = 0;
    Map<String, Actor> xactors = cloneActors();
    List<Actor> catMembers = new LinkedList<Actor>();
    for (String key : xactors.keySet()) {
      Actor to = xactors.get(key);
      if (category.equals(to.getCategory()) && 
            (to.getMessageCount() < to.getMaxMessageCount())) {
        catMembers.add(to);
      }
    }
    // find an actor with lowest message count
    int min = Integer.MAX_VALUE;
    Actor amin = null;
    for (Actor a : catMembers) {
      int mcount = a.getMessageCount();
      if (mcount < min) {
        min = mcount;
        amin = a;
      }
    }
    if (amin != null) {
      count += send(message, from, amin);
    }
    return count;
  }

  @Override
  public int broadcast(Message message, Actor from) {
    int count = 0;
    Map<String, Actor> xactors = cloneActors();
    for (String key : xactors.keySet()) {
      Actor to = xactors.get(key);
      count += send(message, from, to);
    }
    return count;
  }

  public void awaitMessage(AbstractActor a) {
    synchronized (actors) {
      waiters.put(a.getName(), a);
    }
  }

Thread pool initialization

The manager provides a pool of lower-priority daemon threads to allocate to actors to process received messages. (Note that options processing has been omitted for brevity; it is included in the supplied source.)

Listing 39. DefaultActorManager class initialization
protected static int groupCount;

  @Override
  public void initialize(Map<String, Object> options) {
    int count = getThreadCount(options);
    ThreadGroup tg = new ThreadGroup("ActorManager" + groupCount++);
    for (int i = 0; i < count; i++) {
      Thread t = new Thread(tg, new ActorRunnable(), "actor" + i);
      threads.add(t);
      t.setDaemon(true);
      t.setPriority(Math.max(Thread.MIN_PRIORITY, 
         Thread.currentThread().getPriority() - 1));
    }
    running = true;
    for (Thread t : threads) {
      t.start();
    }
  }

Each actor is dispatched by the Runnable implementation in Listing 40. As long as ready actors (that is, actors with pending messages) are available, they are dispatched; otherwise, the thread waits (with a variable timeout) for a message to arrive.

Listing 40. Message processing via a Runnable
public class ActorRunnable implements Runnable {
    public void run() {
      int delay = 1;
      while (running) {
        try {
          if (!procesNextActor()) {
            synchronized (actors) {
              actors.wait(delay * 1000);
            }
            delay = Math.max(5, delay + 1);
          } else {
            delay = 1;
          }
        } catch (InterruptedException e) {
        } catch (Exception e) {
          System.out.printf("procesNextActor exception %s%n", e);
        }
      }
    }
  }

The procesNextActor method first tests to see if any newly created actors exists, and runs one. Otherwise, it tests for a waiting actor. If there are any, it dispatches one actor to process its next message. At most, one message is processed per call. Note that all synchronization is done with the actors field; this reduces the possibility of a deadlock occurring.

Listing 41. Selecting and dispatching the next actor
protected boolean procesNextActor() {
    boolean run = false, wait = false, res = false;
    AbstractActor a = null;
    synchronized (actors) {
      for (String key : runnables.keySet()) {
        a = runnables.remove(key);
        break;
      }
    }
    if (a != null) {
      run = true;
      a.run();
    } else {
      synchronized (actors) {
        for (String key : waiters.keySet()) {
          a = waiters.remove(key);
          break;
        }
      }
      if (a != null) {
        // then waiting for responses
        wait = true;
        res = a.receive();
      }
    }
    return run || res;
  }

Terminate methods

Manager termination is requested by calling either the terminate or terminateAndWait method. terminate signals all threads to stop processing as soon as possible. terminateAndWait also waits for the threads to complete.

Listing 42. DefaultActorManager class termination
@Override
  public void terminateAndWait() {
    terminate();
    for (Thread t : threads) {
      try {
        t.join();
      } catch (InterruptedException e) {
      }
    }
  }

  boolean running;

  @Override
  public void terminate() {
    running = false;
    for(Thread t: threads) {
      t.interrupt();
    }
    synchronized (actors) {
      for (String key : actors.keySet()) {
        actors.get(key).deactivate();
      }
    }
  }

Create methods

The create method family constructs actors and associates them with this manager. A create is supplied with the class of the actor, which must have a default constructor. In addition, actors can be started at creation time or later. Note that this implementation requires all actors to extend AbstractActor .

Listing 43. Creating and starting actors
@Override
  public Actor createAndStartActor(Class<? extends Actor> clazz, String name, 
        Map<String, Object> options) {
    Actor res = createActor(clazz, name, options);
    startActor(res);
    return res;
  }

  @Override
  public Actor createActor(Class<? extends Actor> clazz, String name, 
       Map<String, Object> options) {
    AbstractActor a = null;
    synchronized (actors) {
      if (!actors.containsKey(name)) {
        try {
          a = (AbstractActor) clazz.newInstance();
          a.setName(name);
          a.setManager(this);
        } catch (Exception e) {
          throw e instanceof RuntimeException ? 
             (RuntimeException) e : new RuntimeException(
              "mapped exception: " + e, e);
        }
      } else {
        throw new IllegalArgumentException("name already in use: " + name);
      }
    }
    return a;
  }
}

  @Override
  public void startActor(Actor a) {
    a.activate();
    synchronized (actors) {
      String name = a.getName();
      actors.put(name, (AbstractActor) a);
      runnables.put(name, (AbstractActor) a);
    }
  }

结论

Parting is such sweet sorrow!

In this article, you learned how to use a relatively simple actor system for a variety of common Java programming scenarios and patterns. The μJavaActors library is both flexible and dynamic in behavior, offering a Java-based alternative to more heavyweight actor libraries like Akka.

From the code examples and video simulation, it is clear that μJavaActors can efficiently distribute actor message-processing across a pool of execution threads. Moreover, the user interface makes it immediately obvious if more threads are needed. The interface also makes it easy to determine which actors are starved for work, or whether some actors are overloaded.

DefaultActorManager , the default implementation of the ActorManager interface, guarantees that no actor will process more than one message at a time. It thus relieves the actor author from dealing with any re-entrance considerations. The implementation also does not require synchronization by the actor as long as: (1) the actor only uses private (instance or method local) data and (2) message parameters are written only by message senders, and (3) read only by message receivers.

Two important design parameters of DefaultActorManager are the ratio of threads to actors and the total number of threads to use . There should be at least as many threads as processors on the computer, unless some are reserved for other usage. As threads can be frequently idle (for instance, when waiting on I/O), the correct ratio is often two or more times as many threads as processors. In general, there should be enough actors — really the message rate between actors — to keep the thread pool mostly busy, most of the time. (For best response, some reserve threads should be available; typically an average 75 percent to 80 percent active rate when under load is best.) This means that there usually should be many more actors than threads, as there are times when actors may not have any pending messages to process. Of course, your mileage may vary. Actors that perform actions that wait, such as waiting for a human response, will need more threads. (Threads become dedicated to the actor while waiting and cannot process other messages.)

DefaultActorManager makes good use of Java threads in that a thread is only associated with a particular actor while the actor is processing a message; otherwise, it is free to be used by other actors. This allows a fixed-size thread pool to service an unbounded number of actors. As a result, fewer threads need be created for a given workload. This is important because threads are very heavyweight objects that are often limited to a relatively small number of instances by the host operating system. In this, the μJavaActors library differentiates itself from actor systems that allocate one thread per actor; doing so effectively idles the thread if the actor has no messages to process and possibly limits the number of actor instances that can exist.

The μJavaActors implementation is quite efficient with regard to thread switching. If a new message exists to be processed when message processing is complete, no thread switch occurs; the new message is processed in a repeat of a simple loop. Thus, if there are at least as many messages waiting as threads, no thread becomes idle and thus no switching need occur. If sufficient processors exist (at least one per thread), then it is possible for each thread to be effectively assigned to a processor and never experience a thread switch. Threads will sleep if insufficient buffered messages exist, but this is not significant as the overhead occurs only when no work is pending.

Other actor libraries for the JVM

Other actor solutions for the JVM exist. Table 1 briefly shows how three of them compare with the μJavaActors library:

Table 1. Comparing JVM actor libraries with μJavaActors
名称 看到 描述 Compared with μJavaActors
Kilim http://www.malhar.net/sriram/kilim/ A Java library that supports a multiple-producer, single-consumer mailbox model based on lightweight threads. Kilim requires byte-code adjustment. In μJavaActors, each actor is its own mailbox, so separate mailbox objects are not needed.
Akka http://akka.io/ Attempts to emulate the pattern-matching of actors in functional languages, generally using instanceof type checking (whereas μJavaActors generally uses string equality or regular expression matching). Akka is more functional (eg, supports distributed actors) and thus larger and arguably more complex than μJavaActors.
GPars http://gpars.codehaus.org/Actor Groovy Actor library. Similar to μJavaActors but oriented more toward Groovy developers.

Note that some of the JVM actor solutions in Table 1 add synchronous sends (that is, the sender waits for a reply). While convenient, this can result in reduced message-processing fairness and/or possibly re-entrant calls to an actor. μJavaActors uses POJT (plain old Java threads) and standard thread monitors, which is a more traditional implementation. Some of these other approaches have specialized support to provide their own thread models. μJavaActors is a pure Java library; in order to use it, you need only ensure that its JAR is on the classpath. No byte-code manipulation or other special actions are required.

Enhancing μJavaActors

There is, of course, room to improve or extend the μJavaActors library. I conclude with some possibilities for your interest:

  • Redistribution of pending messages in a category: Currently, messages are assigned round-robin when sent, but not rebalanced afterwards.
  • Allow for priority-based actor execution: Currently, all actors are executed on threads of equal priority; the system would be more flexible if threads (or thread pools) of different priority existed and actors could be assigned to these threads as conditions change.
  • Allow for priority messages: Currently, messages are processed typically in send-order, allowing priority processing would enable more flexible processing.
  • Allow actors to process messages from multiple categories: Currently, only one category at a time is allowed.
  • Optimize the implementation to reduce thread switches and thus improve potential message processing rates: This would come at the cost of more complexity.
  • Distributed actors: Currently, actors must all run in a single JVM; cross-JVM execution would be a powerful extension.

翻译自: https://www.ibm.com/developerworks/java/library/j-javaactors/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值