深入了解 Scala 并发性(scala代码学习第十二天)
2003 年,Herb Sutter 在他的文章 “The Free Lunch Is Over” 中揭露了行业中最不可告人的一个小秘密,他明确论证了处理器在速度上的发展已经走到了尽头,并且将由全新的单芯片上的并行 “内核”(虚拟 CPU)所取代。这一发现对编程社区造成了不小的冲击,因为正确创建线程安全的代码,在理论而非实践中,始终会提高高性能开发人员的身价,而让各公司难以聘用他们。看上去,仅有少数人充分理解了 Java 的线程模型、并发 API 以及 “同步” 的含义,以便能够编写同时提供安全性和吞吐量的代码 —— 并且大多数人已经明白了它的困难所在。
据推测,行业的其余部分将自力更生,这显然不是一个理想的结局,至少不是 IT 部门努力开发软件所应得的回报。
关于本系列关于本系列 Ted Neward 潜心研究 Scala 编程语言,并带您跟他一起徜徉。在这个新的 developerWorks 系列中,您将深入了解 Scala 并看到 Scala 的语言功能的实际效果。在进行相关比较时,Scala 代码和 Java 代码将放在一起展示,但(您将发现)Scala 中的许多内容与您在 Java 编程中发现的任何内容都没有直接关联,而这正是 Scala 的魅力所在!毕竟,如果 Java 代码可以做到的话,又何必学习 Scala 呢?
与 Scala 在 .NET 领域中的姐妹语言 F# 相似,Scala 是针对 “并发性问题” 的解决方案之一。在本期文章中,我讨论了 Scala 的一些属性,这些属性使它更加胜任于编写线程安全的代码,比如默认不可修改的对象,并讨论了一种返回对象副本而不是修改它们内容的首选设计方案。Scala 对并发性的支持远比此深远;现在,我们有必要来了解一下 Scala 的各种库。
并发性基础
在深入研究 Scala 的并发性支持之前,有必要确保您具备了对 Java 基本并发性模型的良好理解,因为 Scala 的并发性支持,从某种程度上说,建立在 JVM 和支持库所提供的特性和功能的基础之上。为此,清单 1 中的代码包含了一个已知的 Producer/Consumer并发性问题(详见 Sun Java Tutorial 的 “Guarded Blocks” 小节)。注意,Java Tutorial 版本并未在其解决方案中使用 java.util.concurrent类,而是择优使用了 java.lang.Object中的较旧的 wait()/notifyAll()方法:
清单 1. Producer/Consumer(Java5 之前)
Java 教程 “缺陷”好奇的读者可能会将此处的代码与 Java Tutorial 中的代码进行比较,寻找它们之间有哪些不同;他们会发现我并未 “同步” put和 take方法,而是使用了存储在 Drop中的 lock对象。其原因非常简单:对象的监测程序永远都不会封装在类的内部,因此 Java Tutorial 版本允许此代码打破此规则(显然很疯狂):def spawn(p: => Unit) = { val t = new Thread() { override def run() = p } t.start() }
……这再一次强调了 by-name 参数的强大之处。
ops.spawn方法的一个缺点在于,它是在 2003 年 Java 5 concurrency 类还不可用的时候编写的。特别是,java.util.concurrent.Executor及其同类的作用是让开发人员更加轻松地生成线程,而不需要实际处理直接创建线程对象的细节。幸运的是,在您自己的自定义库中重新创建 spawn的定义是相当简单的,这需要利用 Executor(或 ExecutorService或 ScheduledExecutorService)来执行线程的实际启动任务。
事实上,Scala 的并发性支持超越了 MailBox和 ops类;Scala 还支持一个类似的 “Actors” 概念,它使用了与 MailBox所采用的方法相类似的消息传递方法,但应用更加全面并且灵活性也更好。但是,这部分内容将在下期讨论。
结束语
Scala 为并发性提供了两种级别的支持,这与其他与 Java 相关的主题极为类似:
首先,对底层库的完全访问(比如说 java.util.concurrent)以及对 “传统” Java 并发性语义的支持(比如说监控程序和 wait()/notifyAll())。
其次,这些基本机制上面有一个抽象层,详见本文所讨论的 MailBox类以及将在本系列下一篇文章中讨论的 Actors 库。
两个例子中的目标是相同的:让开发人员能够更加轻松地专注于问题的实质,而不用考虑并发编程的低级细节(显然,第二种方法更好地实现了这一目标,至少对于没有过多考虑低级细节的人来说是这样的。)
但是,当前 Scala 库的一个明显的缺陷就是缺乏 Java 5 支持;scala.concurrent.ops类应该具有 spawn这样的利用新的 Executor接口的方法。它还应该支持利用新的 Lock接口的各种版本的 synchronized。幸运的是,这些都是可以在 Scala 生命周期中实现的库增强,而不会破坏已有代码;它们甚至可以由 Scala 开发人员自己完成,而不需要等待 Scala 的核心开发团队提供给他们(只需要花费少量时间)。