原文:http://faculty.ycp.edu/~dhovemey/spring2011/cs365/lecture/lecture20.html
原子指令
原子指令是特殊的硬件指令,以不可分的方式对一个或多个内存位置执行操作。无论其他处理器执行什么指令,原子操作都会成功或完全失败。
原子指令可以用来做同步处理。由于原子指令可用于更改共享数据而无需获取和释放锁,因此可以实现更高的并行性。但是,由于它们是低层的,并且只能对数据结构进行小的更新,因此使用它们来实现并行数据结构是一项艰巨的任务。
原子指令的例子:
-
原子增量 atomic increment
-
原子交换寄存器和内存位置 atomic exchange register and
memory location -
比较与交换 compare and swap
比较与交换
比较与交换(CAS)通常用作无锁数据结构的基元。它的行为由以下C函数描述,其中“ATOMIC {…}表示以原子方式执行的代码块:
int compareAndSwap(int * loc,int expectedValue,int valueToStore){
ATOMIC {
int val = * loc;
if(val == expectedValue){
* loc = valueToStore;
}
返回;
}
}
(上面的函数假定内存位置的内容是一个整数,但它可以是任何原始数据类型,包括指针类型。)
Java中的原子指令
java.util.concurrent.atomic中封装 表示内存位置的数据类型,其内存位置可以通过原子机器指令访问(如果主机CPU具有所需的硬件指令)。
这些数据类型类似于内置的“wrapper”数据类型,例如java.lang.Integer,它们用于定义封装原始值的对象。
AtomicInteger是具有原子操作的共享整数,AtomicReference是具有原子操作的共享引用(指针)等。
应用
共享计数器
原子增量操作可用于实现共享计数器。
伪随机数生成器
线性同余伪随机数生成器 (如java.util.Random)的工作原理是使用递推以生成基于初始种子值的一系列整数的值。每次调用者想要获得序列的下一个值时,生成器必须
- 获取当前种子值
- 使用递推方程生成下一个种子
- 存储更新的种子值
- 返回下一个种子值派生的值
步骤1-3必须以原子方式执行,因为当不同的线程同时请求序列的下一个成员时,它们不能返回相同的成员。可以使用互斥锁确保原子性,但如果许多线程同时使用生成器,则产生的竞争可能会降低性能(因为线程争用互斥锁。)步骤1-3必须以原子方式执行,因为当不同的线程同时请求序列的下一个成员时,它们不能返回相同的成员。可以使用互斥锁确保原子性,但如果许多线程同时使用生成器,则产生的竞争可能会降低性能(因为线程争用互斥锁。)
CAS操作可用于实现乐观并发。完成上述算法中的步骤1-2而不获取锁。仅当没有其他线程更改它时,步骤3才使用CAS操作来存储更新的下一个种子值 。如果CAS成功,则执行步骤4并完成操作。如果CAS失败,因为另一个线程更新了种子,则操作返回到步骤1并再次尝试。
队列数据结构
这是一篇描述使用CAS操作而不是互斥锁实现的队列数据结构的文章:Michael,Maged和Scott,Michael。 简单,快速,实用的非阻塞和阻塞并发队列算法,PODC 1996。
这些算法也在Michael Scott的网站上作为伪代码:
http://www.cs.rochester.edu/research/synchronization/pseudocode/queues.html
本文还描述了一种基于锁的队列算法,其中使用单独的锁来保护队列的头部和尾部,允许并行和出列操作并行进行。