当多线程并发遇到 Actor

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

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思源笔记

    1063 引用 • 3453 回帖 • 203 关注
  • Java

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

    3187 引用 • 8213 回帖

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • SSL

    SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS 与 SSL 在传输层对网络连接进行加密。

    70 引用 • 193 回帖 • 431 关注
  • Vditor

    Vditor 是一款浏览器端的 Markdown 编辑器,支持所见即所得、即时渲染(类似 Typora)和分屏预览模式。它使用 TypeScript 实现,支持原生 JavaScript、Vue、React 和 Angular。

    352 引用 • 1815 回帖 • 1 关注
  • InfluxDB

    InfluxDB 是一个开源的没有外部依赖的时间序列数据库。适用于记录度量,事件及实时分析。

    2 引用 • 73 关注
  • Kubernetes

    Kubernetes 是 Google 开源的一个容器编排引擎,它支持自动化部署、大规模可伸缩、应用容器化管理。

    110 引用 • 54 回帖
  • 机器学习

    机器学习(Machine Learning)是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。

    83 引用 • 37 回帖
  • GraphQL

    GraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑。

    4 引用 • 3 回帖 • 9 关注
  • 开源

    Open Source, Open Mind, Open Sight, Open Future!

    408 引用 • 3574 回帖 • 1 关注
  • Elasticsearch

    Elasticsearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于 RESTful 接口。Elasticsearch 是用 Java 开发的,并作为 Apache 许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

    117 引用 • 99 回帖 • 212 关注
  • JWT

    JWT(JSON Web Token)是一种用于双方之间传递信息的简洁的、安全的表述性声明规范。JWT 作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以 JSON 的形式安全的传递信息。

    20 引用 • 15 回帖 • 2 关注
  • BookxNote

    BookxNote 是一款全新的电子书学习工具,助力您的学习与思考,让您的大脑更高效的记忆。

    笔记整理交给我,一心只读圣贤书。

    1 引用 • 1 回帖
  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    334 引用 • 323 回帖 • 2 关注
  • CentOS

    CentOS(Community Enterprise Operating System)是 Linux 发行版之一,它是来自于 Red Hat Enterprise Linux 依照开放源代码规定释出的源代码所编译而成。由于出自同样的源代码,因此有些要求高度稳定的服务器以 CentOS 替代商业版的 Red Hat Enterprise Linux 使用。两者的不同在于 CentOS 并不包含封闭源代码软件。

    238 引用 • 224 回帖 • 1 关注
  • 链书

    链书(Chainbook)是 B3log 开源社区提供的区块链纸质书交易平台,通过 B3T 实现共享激励与价值链。可将你的闲置书籍上架到链书,我们共同构建这个全新的交易平台,让闲置书籍继续发挥它的价值。

    链书社

    链书目前已经下线,也许以后还有计划重制上线。

    14 引用 • 257 回帖
  • App

    App(应用程序,Application 的缩写)一般指手机软件。

    91 引用 • 384 回帖 • 1 关注
  • 链滴

    链滴是一个记录生活的地方。

    记录生活,连接点滴

    153 引用 • 3783 回帖
  • 运维

    互联网运维工作,以服务为中心,以稳定、安全、高效为三个基本点,确保公司的互联网业务能够 7×24 小时为用户提供高质量的服务。

    149 引用 • 257 回帖
  • 钉钉

    钉钉,专为中国企业打造的免费沟通协同多端平台, 阿里巴巴出品。

    15 引用 • 67 回帖 • 339 关注
  • SEO

    发布对别人有帮助的原创内容是最好的 SEO 方式。

    35 引用 • 200 回帖 • 22 关注
  • FlowUs

    FlowUs.息流 个人及团队的新一代生产力工具。

    让复杂的信息管理更轻松、自由、充满创意。

    1 引用 • 4 关注
  • Bug

    Bug 本意是指臭虫、缺陷、损坏、犯贫、窃听器、小虫等。现在人们把在程序中一些缺陷或问题统称为 bug(漏洞)。

    75 引用 • 1737 回帖 • 3 关注
  • 创造

    你创造的作品可能会帮助到很多人,如果是开源项目的话就更赞了!

    179 引用 • 995 回帖 • 1 关注
  • sts
    2 引用 • 2 回帖 • 195 关注
  • Mobi.css

    Mobi.css is a lightweight, flexible CSS framework that focus on mobile.

    1 引用 • 6 回帖 • 733 关注
  • ZeroNet

    ZeroNet 是一个基于比特币加密技术和 BT 网络技术的去中心化的、开放开源的网络和交流系统。

    1 引用 • 21 回帖 • 638 关注
  • Webswing

    Webswing 是一个能将任何 Swing 应用通过纯 HTML5 运行在浏览器中的 Web 服务器,详细介绍请看 将 Java Swing 应用变成 Web 应用

    1 引用 • 15 回帖 • 629 关注
  • 强迫症

    强迫症(OCD)属于焦虑障碍的一种类型,是一组以强迫思维和强迫行为为主要临床表现的神经精神疾病,其特点为有意识的强迫和反强迫并存,一些毫无意义、甚至违背自己意愿的想法或冲动反反复复侵入患者的日常生活。

    15 引用 • 161 回帖
  • AngularJS

    AngularJS 诞生于 2009 年,由 Misko Hevery 等人创建,后为 Google 所收购。是一款优秀的前端 JS 框架,已经被用于 Google 的多款产品当中。AngularJS 有着诸多特性,最为核心的是:MVC、模块化、自动化双向数据绑定、语义化标签、依赖注入等。2.0 版本后已经改名为 Angular。

    12 引用 • 50 回帖 • 474 关注