在…的范围内并发编程,在不借助传统锁的情况下实现线程安全的追求推动了非阻塞算法的广泛采用。实现这些非阻塞方法的一个关键要素是比较和交换(CAS)操作。这篇深入的文章试图揭开Java 语言(一种计算机语言,尤用于创建网站)的CAS机制,揭示了其实现的复杂性,并通过实际示例对其进行了评估。
了解CAS的基础知识
CAS的核心是一个至关重要的原子操作,它允许以线程安全的方式修改共享变量。该操作涉及三个参数:内存位置(地址)、期望值和新值。流程如下:
将指定内存位置的当前值与预期值进行比较。
如果比较结果匹配,新值将自动写入内存位置。
如果比较失败,则操作被视为不成功,表明该内存位置的值已被另一个线程修改。
在Java中,CAS操作封装在由java.util.concurrent包装,如AtomicInteger, AtomicLong,以及AtomicReference。这些类使开发人员更容易创建线程安全的代码,而无需求助于传统的锁定机制。
Java的CAS实现
Java对CAS的实现依赖于低级硬件支持,特别是现代处理器中的比较和交换(CAS)指令。这Unsafe类的使用虽然受到限制,但它在促进直接内存操作方面起着至关重要的作用,这对于实现无锁的原子操作至关重要。
这compareAndSet方法是CAS的基础,它是使用Unsafe类来执行原子更新。让我们仔细看看简化版的compareAndSet方法:
public final class AtomicInteger extends Number implements java.io.Serializable {
private volatile int value;
private static final long valueOffset;
static {
try {
valueOffset = Unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
// Other methods omitted for brevity
}
在这个片段中,valueOffset表示的偏移量value中的字段AtomicInteger班级。静态初始值设定项块试图使用Unsafe班级。这compareAndSet方法然后利用compareAndSwapInt方法来自Unsafe执行原子更新。
这compareAndSwapInt方法是执行CAS操作的基础机制。它需要四个参数:
Object obj:包含要更新的字段的对象
long offset:对象内字段的偏移量
int expected:字段的预期值
int x:要设置的新值
现在,让我们来分解如何compareAndSwapInt作品:
偏移计算:这valueOffset是在类初始化期间使用objectFieldOffset的方法Unsafe班级。此偏移量表示value中的字段AtomicInteger对象。
内存访问:这compareAndSwapInt方法使用计算出的偏移量来访问与value中的字段AtomicInteger对象。
原子比较和交换:实际的CAS操作是自动执行的。它检查指定内存位置的当前值(由对象和偏移量确定)是否与预期值(expect).如果比较成功,则新值(x)自动写入内存位置。
成功或失败:该方法返回一个布尔值,指示CAS操作是成功还是失败。如果比较成功,它将返回true;否则,它返回false.
这种与内存和硬件指令的低级交互使CAS成为实现无锁线程安全的强大工具。
旋转锁中铸造操作
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample1 {
private static AtomicInteger lock = new AtomicInteger(0);
public static void main(String[] args) {
// Simulate a spin lock using CAS
while (!lock.compareAndSet(0, 1)) {
// Spin until the lock is acquired
}
}
}
在本例中,使用CAS实现了自旋锁。该程序尝试使用获取锁compareAndSet.
非阻塞堆栈实现
这个例子展示了如何使用CAS实现非阻塞堆栈,确保推送和弹出操作在不使用锁的情况下是线程安全的。CAS的原子性质确保了多线程可以并发执行这些操作不会损害堆栈的完整性。
import java.util.concurrent.atomic.AtomicReference;
class Node<T> {
T value;
Node<T> next;
Node(T value) {
this.value = value;
this.next = null;
}
}
public class CASExample2 {
private static AtomicReference<Node<Integer>> top = new AtomicReference<>();
public void push(Node<Integer> newNode) {
while (true) {
Node<Integer> currentTop = top.get();
newNode.next = currentTop;
if (top.compareAndSet(currentTop, newNode)) {
break;
}
}
}
public Node<Integer> pop() {
while (true) {
Node<Integer> currentTop = top.get();
if (currentTop == null) {
return null; // Stack is empty
}
Node<Integer> newTop = currentTop.next;
if (top.compareAndSet(currentTop, newTop)) {
return currentTop;
}
}
}
}
在本例中,使用CAS实现了一个非阻塞堆栈,以确保线程安全,而无需传统的锁。让我们来分解关键组件:
Node类别:这Node类表示堆栈中的一个元素,包含一个值和对堆栈中下一个节点的引用。
AtomicReference对于堆栈顶部:这top变量是一个AtomicReference保持对堆栈顶部的引用。它确保对引用的原子更新而不需要锁。
Push操作:
这push方法模拟向堆栈添加新节点。它在一个循环中运行,试图自动更新top参考。
它使用获取堆栈的当前顶部top.get(),将新节点的下一个指针设置为当前顶部,然后尝试更新top引用使用compareAndSet.
如果另一个线程同时修改了top,CAS操作将失败,循环将重试,直到成功更新top原子引用。
Pop操作:
这pop方法模拟从堆栈中移除顶部节点。类似于push操作时,它在一个循环中运行,试图自动更新top参考。
它使用获取堆栈的当前顶部top.get(),检查堆栈是否为空,如果不是,则更新top使用引用下一个节点compareAndSet.
如果另一个线程同时修改了top,CAS操作将失败,循环将重试,直到成功更新top原子引用。
结论
Java的比较和交换(CAS)是一种以非阻塞方式实现原子操作的强大机制。它的实现利用了Unsafe类和低级硬件支持,确保对共享变量的高效和线程安全更新。
这compareAndSwapInt方法是CAS的支柱,通过直接与内存位置交互来执行原子操作。这种交互加上对CAS指令的硬件级支持,有助于CAS在并发编程中的效率和可靠性。
提供的示例展示了CAS在各种情况下的多功能性。无论是递增计数器、实现自旋锁还是构造非阻塞堆栈,CAS都展示了其高效处理并发操作的能力。
随着开发人员深入研究并发编程的复杂性,理解CAS的内部工作变得非常重要。Java致力于提供原子类和利用硬件支持,这强调了它致力于促进健壮和可伸缩的并发应用程序。通过将CAS集成到他们的工具包中,开发人员可以满怀信心地应对并发性挑战,确保其多线程应用程序的完整性和效率。