当多线程并发遇到 Actor

本贴最后更新于 2086 天前,其中的信息可能已经沧海桑田

1

多线程并发的难题

张大胖在做一个银行相关的项目,写了一个 Account 的类,用来表示一个用户的银行账号,根据银行的常规业务,自然要提供两个方法,存款(deposit)和取款(withdraw)。

为了防止多线程并发时导致的数据不一致问题,张大胖给每个方法都加了 synchronized, 那意思很清楚,想进入某个方法执行存款或取款操作,必须得先获得一把锁才行。

(注:为了简化,这里没有做边界条件检查。)

但是在做转账操作的时候,为了保证一致性,必须得把两个账户都加上锁,然后才可以操作,于是张大胖写下了这样的代码,他觉得很简单,立刻就提交给 Bill ,让他 Review。

富有经验的 Bill 立刻就发现了问题,马上对张大胖说:“这样会出现死锁!”

张大胖说:“这么简单的代码,怎么可能有死锁?”

“假设线程 1 做的操作是账户 A 给账户 B 转账, 先锁住了 A 账户, 接下来试图申请 B 账户的锁;

与此同时线程 2 在从 账户 B 给账户 A 转账, 先锁住了 B 账户的锁, 接下来试图申请 A 账户的锁。

两个线程各自持有资源, 然后等待获取对方的资源, 都无法执行下去, 死锁就出现了!”

张大胖无言以对,不得不承认 Bill 是正确的。他问道:“那怎么解决这个问题?”

“非常简单,加锁的时候按次序来就可以了,例如所有的线程,无论是从 A 向 B 转账,还是从 B 向 A 转账,都先获得账号 A 的锁,成功后再获得账户 B 的锁,这样就没问题了。”

张大胖说:“那样代码会变得很古怪啊,还得给两个账户排个顺序,如果不知道背后的思想读起来很痛苦,怪不得人家说多线程编程很难啊。”

Bill 说:“是啊, 其实线程这个东西,就是一段代码的执行而已, 是操作系统层面的概念,可是我们苦逼的程序员不得不来面对它,来背这个多线程并发的锅了。”

2

黑盒子

下班后,张大胖一直在思考这个问题:既然线程是操作系统层面的概念,能不能把线程的概念隐藏起来,然后所有的操作都不用加锁呢? 这样以来编程就会容易得多啊!

本质的问题是什么?

首先是共享的状态,例如 Account 中的 balance ,多个线程都要读写, 其次就是多个线程乱序、并发执行。

能不能换个思路,把这个 Account 对象看成一个黑盒子,你想存款了,就发一个存款的消息过来,想取款就发一个取款的消息过来。

不管是有一个消息,还是有 100 个消息,我统统放到黑盒子的一个队例中,然后让 Account 对象一个个顺序处理不就可以了? 根本不用在方法上加锁!

这样做,其实就是把并发的操作变成了串行的操作而已!

不对,如果调用方把取款消息放下就走, 不等待返回结果, 那就不是同步操作,而是异步操作了!

但是如果取款的时候发现余额不足,怎么通知调用方?嗯,调用方也必须是个黑盒子对象,也向它发送异步消息,这个消息也会在消息队列中存下来,调用方“黑盒子”也会一个个处理。

想到这一层,张大胖激动起来:取款和存款的操作就不用在加锁了,码农们只要考虑黑盒子对消息的处理即可:取出消息,处理消息,向别的黑盒子发送消息, 根本不用考虑线程这样底层的概念了。

3

Actor 模型

第二天张大胖赶紧找到 Bill, 向他炫耀自己的“新发明”。

Bill 不动声色:“小伙子,不错啊,重新发明了轮子!”

“重新发明?”

“是啊,你这个所谓黑盒子,就是所谓 Actor 模型啊! 它最早由 Carl Hewitt 在 1973 定义,其消息传递的方式更加符合面向对象的原始意图, 这一点我想你也体会到了,要不你怎么把他们叫做黑盒子啊。”

“1973 年? 我还没出生。唉,看来这些概念已经被老前辈们都发明完了啊。”

“Actor 属于并发组件模型 ,可以把程序员从多线程并发或线程池等基础概念中解放出来。它有这么几个特点:”

Actor:

就是你说的黑盒子,系统是由很多 Actor 组成。 Actor 之间不共享状态,但是会接收别的 Actor 发送的异步消息,处理的过程中,会改变内部状态,也可能向别的 Actor 发送消息。

Message:

消息是不可变的, 它的发送都是异步的,Actor 内部有个“MailBox”来缓存消息。

MailBox:

Actor 内部缓存消息的邮箱, 其他 Actor 发送的消息都放到这里,然后被本 Actor 处理,类似有多个生成者和一个消费者的队例。

张大胖说:“和我之前的图差不多,看来我确实是重新发明了轮子啊。”

4

用 Actor 实现转账

Bill 笑道:“这个 Actor 看起来很美,但是编程的时候你得刷新一下你的思维才行。 大胖,之前你的转账操作在多线程下不是会出现死锁吗? 你考虑下,如果用 Actor 的思路该怎么写?”

“首先,得有两个 Actor, 这两个 Actor 表示了两个账户,我把它们叫做旺财和小强。”

“然后呢,转账的逻辑怎么处理?”

张大胖想了一会:“既然转账是在两个 Actor 之间发生的,那可以引入一个协调者 Actor,叫做转账管家吧。不过,由于消息都是异步的,转账管家向旺财这个 Actor 发起扣款请求以后,不知道什么时候才能真正执行扣款,也不能立刻知道是否成功,必须得等待啊,这就有点麻烦了。”

Bill 说:“我给你画个流程图,你看看。”

张大胖感慨地说:“原来的多线程并发模型,需要同时锁住两个账户,然后才能进行转账。现在每个 Actor 都独立,也把这个转账给搞定了。”

Bill 说:“其实对于转账管家来说,对每个转账的消息,内部是隐含一个流程状态的,就是先向某个账户扣款,成功以后再向另一个账户增加,最后给调用者返回状态,这个次序是不能乱的。看到图中那个 Transaction ID 没有(Tx01),就是用来跟踪这个转账的事务。”

4

漏洞

“我发现了一个漏洞,你这个转账虽然看起来很美,没有加锁,但是和原来的是有区别的,原来多线程思路是会把旺财和小强的账户同时锁住,然后转账,在这个过程中,别人是不能操作这两个账号的! 而你的 Actor 方案中,当转账管家给旺财发消息扣款的时候,小强其实是自由的,如果这时候小强的账户被冻结,那你的转账管家还得回滚旺财的扣款,这多麻烦啊。”

Bill:“哈哈,你小子还挺机灵的嘛,看出了这个问题,Actor 模型非常适用于多个组件独立工作,相互之间仅仅依靠消息传递的情况。如果想在多个组件之间维持一致的状态(比如咱们例子中的转账),那就不爽了。”

“那怎么解决这个问题?”

“那必须得用一些特殊手段了,有些实现 Actor 的框架,例如 Akka,专门提供了像 Coordinated /Transactor 这样的机制来处理这个问题。有空的话给你仔细讲讲。”

“好吧,我回头看看这个 Akka, 对了, Actor 虽然对用户隐藏了线程, 但是总得有线程来处理消息吧。” 张大胖问道。

“那是肯定的,线程本质上就是一段代码的执行,每个 Actor 在处理消息的时候,肯定得和线程关联才行,只不过 Actor 系统把线程这个概念给隐藏了。

“有哪些系统实现了 Actor?” 张大胖接着问。

“其实最著名的就是 Erlang 了,Actor 模型可以说是它的基础,除了我们上面所说的,还可以让 Actor 之间建立关联,例如让一个 Actor 去监控另外一些 Actor 工作,如果那些 Actor 崩溃了,就新建一个 Actor 继续工作。在 Java 领域,刚才提到的 Akka 是比较知名的一个 Actor 框架。

转发自:https://mp.weixin.qq.com/s/mzZatZ10Rh19IEgQvbhGUg

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1083 引用 • 3461 回帖 • 263 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3169 引用 • 8208 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • RESTful

    一种软件架构设计风格而不是标准,提供了一组设计原则和约束条件,主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

    30 引用 • 114 回帖
  • OnlyOffice
    4 引用 • 15 关注
  • Ruby

    Ruby 是一种开源的面向对象程序设计的服务器端脚本语言,在 20 世纪 90 年代中期由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)设计并开发。在 Ruby 社区,松本也被称为马茨(Matz)。

    7 引用 • 31 回帖 • 189 关注
  • 房星科技

    房星网,我们不和没有钱的程序员谈理想,我们要让程序员又有理想又有钱。我们有雄厚的房地产行业线下资源,遍布昆明全城的 100 家门店、四千地产经纪人是我们坚实的后盾。

    6 引用 • 141 回帖 • 569 关注
  • 单点登录

    单点登录(Single Sign On)是目前比较流行的企业业务整合的解决方案之一。SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

    9 引用 • 25 回帖 • 1 关注
  • 安装

    你若安好,便是晴天。

    131 引用 • 1184 回帖
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    19813 引用 • 75881 回帖
  • Markdown

    Markdown 是一种轻量级标记语言,用户可使用纯文本编辑器来排版文档,最终通过 Markdown 引擎将文档转换为所需格式(比如 HTML、PDF 等)。

    165 引用 • 1471 回帖
  • 宕机

    宕机,多指一些网站、游戏、网络应用等服务器一种区别于正常运行的状态,也叫“Down 机”、“当机”或“死机”。宕机状态不仅仅是指服务器“挂掉了”、“死机了”状态,也包括服务器假死、停用、关闭等一些原因而导致出现的不能够正常运行的状态。

    13 引用 • 82 回帖 • 51 关注
  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    198 引用 • 120 回帖
  • MongoDB

    MongoDB(来自于英文单词“Humongous”,中文含义为“庞大”)是一个基于分布式文件存储的数据库,由 C++ 语言编写。旨在为应用提供可扩展的高性能数据存储解决方案。MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似 JSON 的 BSON 格式,因此可以存储比较复杂的数据类型。

    90 引用 • 59 回帖 • 4 关注
  • Vim

    Vim 是类 UNIX 系统文本编辑器 Vi 的加强版本,加入了更多特性来帮助编辑源代码。Vim 的部分增强功能包括文件比较(vimdiff)、语法高亮、全面的帮助系统、本地脚本(Vimscript)和便于选择的可视化模式。

    28 引用 • 66 回帖 • 2 关注
  • OkHttp

    OkHttp 是一款 HTTP & HTTP/2 客户端库,专为 Android 和 Java 应用打造。

    16 引用 • 6 回帖 • 53 关注
  • jsDelivr

    jsDelivr 是一个开源的 CDN 服务,可为 npm 包、GitHub 仓库提供免费、快速并且可靠的全球 CDN 加速服务。

    5 引用 • 31 回帖 • 52 关注
  • 程序员

    程序员是从事程序开发、程序维护的专业人员。

    541 引用 • 3529 回帖
  • Kotlin

    Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,由 JetBrains 设计开发并开源。Kotlin 可以编译成 Java 字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。在 Google I/O 2017 中,Google 宣布 Kotlin 成为 Android 官方开发语言。

    19 引用 • 33 回帖 • 43 关注
  • Oracle

    Oracle(甲骨文)公司,全称甲骨文股份有限公司(甲骨文软件系统有限公司),是全球最大的企业级软件公司,总部位于美国加利福尼亚州的红木滩。1989 年正式进入中国市场。2013 年,甲骨文已超越 IBM,成为继 Microsoft 后全球第二大软件公司。

    103 引用 • 126 回帖 • 442 关注
  • 笔记

    好记性不如烂笔头。

    306 引用 • 782 回帖
  • LeetCode

    LeetCode(力扣)是一个全球极客挚爱的高质量技术成长平台,想要学习和提升专业能力从这里开始,充足技术干货等你来啃,轻松拿下 Dream Offer!

    209 引用 • 72 回帖
  • BAE

    百度应用引擎(Baidu App Engine)提供了 PHP、Java、Python 的执行环境,以及云存储、消息服务、云数据库等全面的云服务。它可以让开发者实现自动地部署和管理应用,并且提供动态扩容和负载均衡的运行环境,让开发者不用考虑高成本的运维工作,只需专注于业务逻辑,大大降低了开发者学习和迁移的成本。

    19 引用 • 75 回帖 • 617 关注
  • 服务器

    服务器,也称伺服器,是提供计算服务的设备。由于服务器需要响应服务请求,并进行处理,因此一般来说服务器应具备承担服务并且保障服务的能力。

    124 引用 • 580 回帖
  • Ubuntu

    Ubuntu(友帮拓、优般图、乌班图)是一个以桌面应用为主的 Linux 操作系统,其名称来自非洲南部祖鲁语或豪萨语的“ubuntu”一词,意思是“人性”、“我的存在是因为大家的存在”,是非洲传统的一种价值观,类似华人社会的“仁爱”思想。Ubuntu 的目标在于为一般用户提供一个最新的、同时又相当稳定的主要由自由软件构建而成的操作系统。

    123 引用 • 168 回帖 • 1 关注
  • GitHub

    GitHub 于 2008 年上线,目前,除了 Git 代码仓库托管及基本的 Web 管理界面以外,还提供了订阅、讨论组、文本渲染、在线文件编辑器、协作图谱(报表)、代码片段分享(Gist)等功能。正因为这些功能所提供的便利,又经过长期的积累,GitHub 的用户活跃度很高,在开源世界里享有深远的声望,并形成了社交化编程文化(Social Coding)。

    207 引用 • 2031 回帖
  • IDEA

    IDEA 全称 IntelliJ IDEA,是一款 Java 语言开发的集成环境,在业界被公认为最好的 Java 开发工具之一。IDEA 是 JetBrains 公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。

    180 引用 • 400 回帖 • 1 关注
  • 心情

    心是产生任何想法的源泉,心本体会陷入到对自己本体不能理解的状态中,因为心能产生任何想法,不能分出对错,不能分出自己。

    59 引用 • 369 回帖 • 2 关注
  • V2Ray
    1 引用 • 15 回帖
  • Sandbox

    如果帖子标签含有 Sandbox ,则该帖子会被视为“测试帖”,主要用于测试社区功能,排查 bug 等,该标签下内容不定期进行清理。

    379 引用 • 1221 回帖 • 589 关注