Java 理论与实践: 流行的原子
新原子类是 java.util.concurrent 的隐藏精华
在 之前,如果不使用本机代码,就不能用 语言编写无等待、无锁定的算法。在
中添加原子变量类之后,这种情况发生了变化。请跟随并行专家 一
起,了解这些新类如何使用 语言开发高度可伸缩的无阻塞算法。您可以在本文的 论坛中与作者或其
他读者共享您对本文的看法。(也可以通过单击文章顶部或者底部的 讨论链接来访问讨论。)
十五年前,多处理器系统是高度专用系统,要花费数十万美元(大多数具有两个到四个处理器)。现在,
多处理器系统很便宜,而且数量很多,几乎每个主要微处理器都内置了多处理支持,其中许多系统支持数
十个或数百个处理器。
要使用多处理器系统的功能,通常需要使用多线程构造应用程序。但是正如任何编写并发应用程序的人可
以告诉你的那样,要获得好的硬件利用率,只是简单地在多个线程中分割工作是不够的,还必须确保线程
确实大部分时间都在工作,而不是在等待更多的工作,或等待锁定共享数据结构。
问题:线程之间的协调
如果线程之间
不
需要协调,那么几乎没有任务可以真正地并行。以线程池为例,其中执行的任务通常相
互独立。如果线程池利用公共工作队列,则从工作队列中删除元素或向工作队列添加元素的过程必须是线
程安全的,并且这意味着要协调对头、尾或节点间链接指针所进行的访问。正是这种协调导致了所有问题。
标准方法:锁定
在 语言中,协调对共享字段的访问的传统方法是使用同步,确保完成对共享字段的所有访问,同时
具有适当的锁定。通过同步,可以确定(假设类编写正确)具有保护一组给定变量的锁定的所有线程都将
拥有对这些变量的独占访问权,并且以后其他线程获得该锁定时,将可以看到对这些变量进行的更改。弊
端是如果锁定竞争太厉害(线程常常在其他线程具有锁定时要求获得该锁定),会损害吞吐量,因为竞争
的同步非常昂贵。(:对于现代 而言,无竞争的同步现在非常便
宜。
基于锁定的算法的另一个问题是:如果延迟具有锁定的线程(因为页面错误、计划延迟或其他意料之外的
延迟),则
没有
要求获得该锁定的线程可以继续运行。
还可以使用可变变量来以比同步更低的成本存储共享变量,但它们有局限性。虽然可以保证其他变量可以
立即看到对可变变量的写入,但无法呈现原子操作的读修改写顺序,这意味着(比如说)可变变量无法
用来可靠地实现互斥(互斥锁定)或计数器。
使用锁定实现计数器和互斥
假如开发线程安全的计数器类,那么这将暴露 !、 !和 " !操作。清单 #显
示了如何使用锁定(同步)实现该类的例子。注意所有方法,甚至需要同步 !,使类成为线程安全的
类,从而确保没有任何更新信息丢失,所有线程都看到计数器的最新值。
清单 1. 同步的计数器类
$%%&'"()
$*