薬屋 | iren
1336 words
7 minutes
Java并发编程-原子变量与非阻塞同步机制
NOTE本篇笔记基于《Java并发编程实战》第15章 - 原子变量与非阻塞同步机制
1. 锁的劣势
在并发编程中,锁是一种常见的同步机制,但是从前面几章的内容中我们不难发现,用锁来进行同步时会存在一些问题:
- 性能开销较大: 锁会引入线程的上下文切换和调度开销,线程竞争激烈时,锁会导致大量的阻塞和线程切换,从而显著降低性能。
- 潜在的死锁问题: 锁的使用不当容易导致死锁。例如,多个线程循环等待对方持有的锁,程序会陷入不可恢复的状态。
- 可伸缩性差: 锁在高并发场景下会导致竞争问题,线程数越多,锁的竞争越激烈,导致系统性能下降。并且在多核 CPU 中,锁的使用会限制程序的并行性。
- 阻塞线程会导致资源浪费: 锁的阻塞机制会让线程进入等待状态,占用系统资源(如线程栈、内核资源)。阻塞线程需要等待重新调度,这增加了系统的资源消耗和延迟。
- 锁释放的顺序难以控制: 在复杂程序中,如果多个线程需要按照一定顺序访问资源,锁可能导致线程调度的不可预测性,增加了设计复杂性。
因此,现在并发领域的大多数研究都侧重于非阻塞算法,以换取更好的可伸缩性和活跃性。
2. Compare-And-Swap
CAS 是一种基于硬件指令的原子操作,主要用于在多线程环境下更新共享变量。CAS 是由 CPU 提供的指令级支持(如 cmpxchg
指令),因此它在硬件层面保证了操作的原子性。
CAS 包含以下三个操作数:
- 内存位置 (V): 需要操作的变量的内存地址。
- 期望值 (E): 线程希望变量当前的值。
- 新值 (N): 线程准备写入的值。
执行过程: 比较内存中的值 V 是否等于期望值 E
- 如果相等:说明没有其他线程修改变量,将新值 N 写入内存。
- 如果不相等:说明有其他线程修改了变量,不执行更新,返回当前值或失败标志。
在下面这段代码中,就借助CAS实现了一个非阻塞的计数器。首先,通过 value.get()
获取当前计数值,赋值给 v
。然后调用 compareAndSwap(v, v + 1)
试图将当前值更新为 v + 1
。如果当前值等于 v
,说明没有其他线程修改值,则更新成功,compareAndSwap
返回旧值 v
,跳出循环。如果当前值不等于 v
,说明其他线程在此期间修改了值,compareAndSwap
返回的值与 v
不相等,继续循环到更新成功。最后返回新值 v + 1
。
public class CasCounter {
private SimulatedCAS value;
public int getValue() {
return value.get();
}
public int increment() {
int v;
do {
v = value.get();
} while (v != value.compareAndSwap(v, v + 1));
return v + 1;
}
}
潜在的问题
- ABA 问题: CAS 判断的是值是否变化,而非对象的整体状态。当一个变量从值 A 变为 B,再回到 A 时,CAS 无法检测到变化。
- 自旋: 如果 CAS 操作失败,线程会不断重试,称为自旋操作。这可能导致 CPU 占用率升高,特别是在高并发环境下。
3. 原子变量类
Java 中的原子变量类是线程安全的类,用于高效地执行原子性操作,避免了使用锁来实现线程同步。其特点如下所示:
- 无锁线程安全: 使用硬件支持的 CAS 操作实现,避免了锁的开销。
- 高效: 在高并发场景中性能优于传统的锁机制。
- 简单易用: 提供多种常见的原子操作(如增减、更新、替换)。
常用的原子变量类有以下几种:
AtomicInteger
:用于操作int
类型的变量。AtomicLong
:用于操作long
类型的变量。AtomicReference<T>
:用于原子更新引用类型的变量。AtomicBoolean
:用于操作boolean
类型变量。
4. 非阻塞算法
非阻塞的栈是一种线程安全的数据结构,通过无锁算法实现多线程间的并发访问。非阻塞栈使用CAS操作替代传统锁机制,避免线程阻塞,从而提升高并发场景下的性能。
核心数据结构
- 栈使用一个链表实现,每个节点包含两个字段:
value
: 存储栈的值。next
: 指向下一个节点的指针。
- 栈顶指针(
top
)使用AtomicReference
包装,确保原子性操作。
Push 操作
- 创建一个新节点。
- 设置新节点的
next
指向当前栈顶。 - 使用 CAS 将栈顶更新为新节点。
Pop 操作
- 获取当前栈顶。
- 使用 CAS 将栈顶更新为当前栈顶的
next
。 - 返回当前栈顶的值。
实现代码:
public class NonBlockingStack<T> {
private AtomicReference<Node<T>> top = new AtomicReference<>();
private static class Node<T> {
T value;
Node<T> next;
Node(T value) {
this.value = value;
}
}
public void push(T value) {
Node<T> newNode = new Node<>(value);
Node<T> currentTop;
do {
currentTop = top.get();
newNode.next = currentTop;
} while (!top.compareAndSet(currentTop, newNode));
}
public T pop() {
Node<T> currentTop;
do {
currentTop = top.get();
if (currentTop == null) {
return null;
}
} while (!top.compareAndSet(currentTop, currentTop.next));
return currentTop.value;
}
}
Java并发编程-原子变量与非阻塞同步机制
https://mj3622.github.io/posts/学习笔记/java并发编程/原子变量与非阻塞同步机制/