Actor模型:面向对象原生的并发模型

前言

感谢极客时间 王宝令老师的 并发系列课程

有门计算机专业课叫做面向对象编程, 按照面向对象编程理论,对象之间通信需要依靠消息,而实际上,像 C++、Java 这些面向对象的语言, 对象之间的通信,依靠的是对象方法。对象方法和过程语言里的函数本质上没有区别,有入参、出参,思维方式很相似,使用起来都很简单。那面向对象里的消息是否就等价于面向对象语言里的对象方法呢?很长一段时间里,我都以为对象方法是面向对象理论中消息的一种实现, 直到接触到 Actor 模型,才明白消息压根不是这个实现法。

Hello Actor 模型

Actor 模型本质上是一种计算模型,基本的计算单元称之为Actor ,换言之,在Actor 模型中,所有的计算都是在Actor 中执行的。在面向对象编程里面,一切都是对象,在Actor模型里面,一切都是Actor ,并且Actor 之间完全隔离的,不会共享任何变量。

当看到 “不共享任何变量”的时候, 你一定会眼前一亮,并发问题的根源就在于共享变量,而 Actor 模型中 Actor 之间不共享变量, 那用 Actor 模型解决并发问题, 一定是相当顺手,的却是这样,所以很多人把Actor 模型定义为一种并发计算模型。其实Actor 模型早在1973年就 被提出来了,只是知道最近几年才被关注,一个主要原因就在于他是解决并发问题的利器,而最近几年随着多核处理器的发展,并发问题被推到了风口浪尖。

但是 Java 语言本身并不支持 Actor 模型, 所以如果你想在 Java 语言里使用 Actor 模型, 就要借助第三方类库,目前完备支持Actor 模型而且比较成熟的类库就是Akka 了,在详细介绍 Actor 模型之前,我们就先基于 Akka 写一个 Hello World 程序,让你对 Actor 模型先有个感官的印象。

以Java项目为例,首先 Pom 中加入依赖:

        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-actor_2.13</artifactId>
            <version>2.5.23</version>
        </dependency>

Maven 依赖版本查询


public class AkkaApplicationMain {

    public static void main(String[] args) {

        ActorSystem system = ActorSystem.create("HelloSystem");

        ActorRef helloActor = system.actorOf(Props.create(HelloActor.class));
        //发送消息给Actor
        helloActor.tell("Actor", ActorRef.noSender());

    }

    static class HelloActor extends UntypedAbstractActor {
        @Override
        public void onReceive(Object message) throws Throwable {
            System.out.println("Helllo : " + message);
        }
    }
}

在上面的示例中,首先创建一个ActorSystem (Actor 不能脱离 ActorSystem 存在); 之后创建了一个 HelloActor,Akka 中创建 Actor 并不是 new 一个对象出来,而是通过调用 system.actorOf() 方法创建的,该方法返回的是 ActorRef,而不是 HelloActor; 最后通过调用 ActorRef 的 tell() 方法给 HelloActor 发送了一条消息 “Actor” 。

通过这个例子,你会发现Actor 模型和面向对象编程契合度非常高,完全可以用Actor 类比面向对象编程里面的对象,而且Actor 之间的通信方式完美遵循了消息机制,而不是通过对象方法来实现对象之间的通信。那Actor 中的消息机制和面向对象语言里的对象方法有什么区别呢?

消息和对象方法的区别

在没有计算机的时代,异地的交流主要靠写信,但是信件发出去之后,也许会在邮寄过程中丢失了,也可能寄到后,对方一直没有时间写回信……总之任何结果都不可控了。

Actor 中的消息机制,就可以类比这现实世界里的写信,Actor 内部有一个邮箱,接收到的消息都是先保存到邮箱,如果邮箱里有积压消息,那么新收到的消息就不会马上得到处理,也正是因为Actor 使用单线程处理消息,所以不会出现并发问题。你可以把Actor 内部的工作模式想象成只有一个消费者线程的生产者-消费者模式。

所以,在Actor 模型里,发送的消息仅仅时把消息发送出去,接收消息的Actor 在接收到消息后,也不一定会立即处理,也就是说Actor 中的消息机制完全是异步的,而调用对象方法是同步的, 对象方法return之前,调用方一直是等待的。

除此之外,调用对象方法还需要持有对象引用,所有的对象必须在同一个进程中,而在Actor 中发送消息,类似与写信,只要知道对方地址就好了,发送和接收消息的Actor 可以不在一个进程中,也可以不在同一台机器上。一位内Actor 不仅适用于并发计算,而且适用于分布式计算。

Actor 的规范化定义

通过上面介绍,你应该已经对Actor有感官印象了,下面我们来看看Actor 的规范胡定义;Actor 是一种基础的计算单元,包括三部分能力,分别是:

  1. 处理能力,处理接收到的信息
  2. 发送消息给其他的Actor
  3. 确定如何处理下一条消息

其中前两条还是很好理解的,最后一条该如何理解呢?就是我们前面说过的Actor 具备存储能力,它有自己的内部状态,所以你可以把Actor 看作一个状态机,把Actor 处理消息看作是触发状态机的状态变化,而状态的变化往往要基于上一个状态,触发状态机发生变化的时刻,上一个状态必须是明确的,所以确定如何处理下一条消息,本质上不过是改变内部状态。

在多线程里面,由于可能存在竞态条件,所以根据当前状态确定如何处理下一条消息还是有难度的,需要各种同步工具,但是在Actor 模型里,由于是单线程处理,所以不存在竞态条件问题。

用 Actor 实现累加器

支持并发的累加器可能是最具代表性的并发问题了,可以基于互斥锁实现。也可以基于原子类来实现,但今天我们要尝试用Actor 来实现。

public class CounterActorApplication {

    public static void main(String[] args) throws Exception {

        ActorSystem system = ActorSystem.create("CounterSystem");
        //4 个线程生产消息
        ExecutorService service = Executors.newFixedThreadPool(4);

        ActorRef counterActor = system.actorOf(Props.create(CounterActor.class));

        for (int i = 0; i < 4; i++) {
            service.execute(() -> {
                for (int j = 0; j < 10000; j++) {
                    counterActor.tell(1, ActorRef.noSender());
                }
            });
        }
        //关闭线程池
        service.shutdown();
        //等待执行结果
        Thread.sleep(10000);
        //打印结果
        counterActor.tell("", ActorRef.noSender());

    }

    static class CounterActor extends UntypedAbstractActor {

        private int counter = 0;

        //如果接收到的是数字型,则进行累加操作
        //否则直接打印
        @Override
        public void onReceive(Object message) throws Throwable {
            if (message instanceof Number) {
                counter += ((Number) message).intValue();
            } else {
                System.out.println(counter);
            }
        }
    }
}

输出结果:
40000

总结

Actor 模型是一种非常简单的计算模型,其中Actor 是最基本的计算单元,Actor 之间是通过消息进行通信,Actor 与面向对象编程中的对象匹配度非常高,在面向对象编程里面,系统由类似于生物细胞那样的构成,对象之间也是通过消息进行通信,所以在面向对象语言里使用Actor 模型基本上不会有违和感。

在Java 领域,除了可用Akka 来支持Actor 模型外,还可以使用Vert.x, 不过相对来说Vert.x, 更像是Actor 模型的隐式实现,对应关系不像Akka 那样明显,不过本质上也是一种Actor 模型。

Actor 可以创建新的Actor ,这些Actor 最终会呈现出一个树状结构,非常像现实世界里的组织结构,所以利用Actor 模型来对程序进行建模,和现实世界匹配度非常高,Actor 模型跟现实世界一样都是异步模型,理论上不保证消息百分百送达,也不保证消息的顺序和发送的顺序一致的。甚至不会保证消息百分百处理,虽然Actor 实现厂商都在努力解决这些问题。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容

  • 传统的游戏服务器要么是单线程要么是多线程,过去几十年里CPU一直遵循摩尔定律发展,带来的结果是单核频率越来越高。而...
    JunChow520阅读 66,548评论 14 58
  • 前言 一 不得不说的Actor模型 1.1 Actor模型的诞生与发展 1.2 Actor模型是什么? 1.3 A...
    hedgehog1112阅读 413评论 0 0
  • 持久化 当我们在集群系统中,一台机器向另一台机器发送一段数据,负责接收的机器在接收数据前突然宕机,就会造成数据丢失...
    mango_knight阅读 4,539评论 0 4
  • Actor系统的实体 在Actor系统中,actor之间具有树形的监管结构,并且actor可以跨多个网络节点进行透...
    JasonDing阅读 3,341评论 2 6
  • 这是一个最好的时代,也是一个最坏的时代。狄更斯如是说。 生活在21世纪的我们,毫无疑问都承受着时代带...
    梦遣看花人阅读 252评论 0 2