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

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

即使使用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)   
[m
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值