使用Akka Actor来替换Java的Synchronized同步代码

Synchronized是Java非常常见的一种并发机制。哪怕我们不见得会直接用到,synchronzied仍在很多的公用库里会用到。使用Synchronized会有一些,其中的一个问题是,Synchronized是一种阻塞操作,带来了复杂性。本文将通过一种简单的方式来说明此问题,并说明选择Akka Actor获得更好、更易维护的并发性代码的理由。

考虑以下样例代码:

int x; if (x > 0) { return true; } else { return false; }

如果x为正数,则返回true 。很简单的一段代码。再考虑一下下面的计算器的代码:

x++;

以上代码都很简单,但如果在多线程环境下,代码可能会有很大的问题。

在第一个示例中,true或false不是由x的值确定的,而是由if判断确定。因此,如果在第一个线程通过if判断之后另一个线程将x更改为负数,即使x不再为正数,我们仍然会变为true。

第二个例子很具有欺骗性。尽管只是一行代码,但实际上有三个操作:读取x,对其进行递增并返回更新后的值。如果两个线程恰好同时运行,则更新的值可能会丢失。

当不同的线程同时访问和修改同一个变量的时候,就出现了竞争条件。如果我们仅只是想构建一个计数器,则Java提供了线程安全的Atomic变量,其中包括Atomic Integer,我们可以将其用于此目的。但是,Atomic仅适用于单个变量。如何使多个操作原子化?

第一反应是通过使用Synchronized块。看一个更详细的例子: 

int x; public int withdraw(int deduct){ int balance = x - deduct; if (balance > 0) { x = balance; return deduct; } else { return 0; } }

上面的代码实现了一种基本的提取现金的处理过程。多线程情况上,很有可能会有很大问题:即使余额不足,两个线程同时运行也可能导致银行提供两次提款。

下面的代码是加上同步块之后的代码:

volatile int x; public int withdraw(int deduct){ synchronized(this){ int balance = x - deduct; if (balance > 0) { x = balance; return deduct; } else { return 0; } } }

同步块的想法很简单。一个线程进入并锁定,其它线程必须等待。锁是一个对象,在我们的例子中是this。进入同步块的代码执行完成后,将锁释放并传递给另一个线程,然后该线程将执行相同的操作。另,请注意需要使用关键字volatile,以防止线程使用变量x的本地CPU缓存。

加入同步块后,哪怕在多线程复杂的执行环境下,银行也不会意外提供多次提款。但是,当有越来越多的同步块和并发锁存在的时候,这种代码结构往往带来复杂的代码逻辑,而处理多个同步锁的过程也容易引发错误。多个同步块可能会在不经意间互相持有同步锁并锁定整个应用。还有一个非常重要的情况是,Synchronized同步块在很多情况下存在效率问题:当一个线程运行的时候,所有其它线程都需要等待。

和上面的同步块类似的是队列,可以考虑使用队列来实现相同的功能。想象一个电子邮件系统,发送电子邮件时,会将电子邮件拖放到收件人的邮箱中,不必等到接收方阅读就直接返回。Actor模型和Akka框架就是基于此。

Actor封装状态和行为。但是,与OOP的封装不同,actor根本不公开其状态和行为。actor相互交流的唯一方法是交换消息。传入邮件将被放入邮箱中,并按照先进先出的顺序进行进行处理。

下面代码是Akka和Scala中重写的示例:

case class Withdraw(deduct: Int) class SlaveActor extends Actor { var x = 10; def receive: Receive = { case Withdraw(deduct) => val r = withdraw(deduct) } } class BossActor extends Actor { var slave = context.actorOf(Props[SlaveActor]) slave ! Withdraw(6) slave ! Withdraw(9) }

SlaveActor负责具体的工作,而BossActor负责向SlaveActor发送命令。sign(tell)是一个参与者将消息异步发送到另一个参与者的两种方法之一(另一种是ask)。tell在执行时不等待答复。因此,BossActor告诉SlaveActor做两次撤单。这些消息到达slave的接收器,其中的每个消息都会被相应的处理程序处理。这种情况下,Withdraw执行withdraw执行金额扣除操作。操作完成后,将前行到队列中的下一条消息。

以上代码改动带来了什么优势?首先,不需要担心锁和使用原子/并发类型带来的线程安全性问题。Actor的封装和排队机制已经保证了线程安全性。线程只是发送消息就返回,也不需要再等待。结果稍后通过Ask或Tell传递,模型很简单,也很有效。

Akka基于JVM,在Scala和Java中均可使用。本文并未对Java与Scala语言进行对比,但Scala的模式匹配和函数式编程在管理Actor的数据消息传递方面很有用,可以避免Java的方括号和分号,可以编写出较短、但同样有效的代码。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
内容简介 本书将尝试帮助入门级、中级以及高级读者理解基本的分布式计算概念,并且展示 如何使用 Akka 来构建具备高容错性、可以横向扩展的分布式网络应用程序。Akka 是一 个强大的工具集,提供了很多选项,可以对在本地机器上处理或网络远程机器上处理的 某项工作进行抽象封装,使之对开发者不可见。本书将介绍各种概念,帮助读者理解 网络上各系统进行交互的困难之处,并介绍如何使用 Akka 提供的解决方案来解决这些 问题。 作者简介 Jason Goodwin 是一个基本上通过自学成才的开发者。他颇具企业家精神,在学校 学习商学。不过他从 15 岁起就开始学习编程,并且一直对技术保持着浓厚的兴趣。这对 他的职业生涯产生了重要的影响,从商学转向了软件开发。现在他主要从事大规模分布 式系统的开发。在业余时间,他喜欢自己原创电子音乐。 他在 mDialog 公司第一次接触到 Akka 项目。mDialog 是一家使用 Scala/Akka 的公司, 为主流出版商提供视频广告插入软件。这家公司最终被 Google 收购。他同时还是一名很 有影响力的“技术控”,将 Akka 引入加拿大一家主要的电信公司,帮助该公司为客户提 供容错性更高、响应更及时的软件。除此之 外,他还为该公司中的一些团队教授 Akka、 函数式以及并发编程等知识。 目录 第 1 章 初识 ActorAkka 工具集以及 Actor 模型的介绍。 第 2 章 Actor 与并发:响应式编程。Actor 与 Future 的使用。 第 3 章 传递消息:消息传递模式。 第 4 章 Actor 的生命周期—处理状态与错误:Actor 生命周期、监督机制、Stash/ Unstash、Become/Unbecome 以及有限自动机。 第 5 章 纵向扩展:并发编程、Router Group/Pool、Dispatcher、阻塞 I/O 的处理以 及 API。 第 6 章 横向扩展—集群化:集群、CAP 理论以及 Akka Cluster。 第 7 章 处理邮箱问题:加大邮箱负载、不同邮箱的选择、熔断机制。 第 8 章 测试与设计:行为说明、领域驱动设计以及 Akka Testkit。 第 9 章 尾声:其他 Akka 特性。下一步需要学习的知识。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值