Java并发容器和原子类
1. Java并发容器
1.1. 并发容器概览
- Java 并发容器是 Java 并发包(
java.util.concurrent
)的重要组成,1.5 后提供了丰富的线程安全容器。 - 分类仍为四大类:
-
- List
- Map
- Set
- Queue
1.2. 同步容器(synchronized-based)
示例:
List list = Collections.synchronizedList(new ArrayList<>());
Set set = Collections.synchronizedSet(new HashSet<>());
Map map = Collections.synchronizedMap(new HashMap<>());
特点:
- 基于
synchronized
实现,保证线程安全。 - 也包括早期的 Vector、Stack、Hashtable 等。
注意事项
1. 组合操作非原子,如:
if (!list.contains(x)) list.add(x); // 非原子!
➤ 推荐封装为原子方法并加锁,例如:
synchronized boolean addIfNotExist(T t) { ... }
2. 遍历时要手动加锁,否则存在并发修改问题:
synchronized (list) {Iterator i = list.iterator();while (i.hasNext()) foo(i.next());
}
1.3. 并发容器(concurrent-based)
优势:
- 使用非阻塞算法、分段锁等机制,性能远优于同步容器。
- 仍然是 List、Map、Set、Queue 四类,但实现更复杂更优化。
1. List
CopyOnWriteArrayList
- 写时复制:每次写操作都会复制一份新数组,写完后替换旧引用。
- 适用于读多写少,如监听器列表、缓存等。
使用注意
- 写操作性能差(因为复制数组)。
- 读操作可能看到旧数据(弱一致性)。
- 其迭代器是只读快照,不支持增删改。
2. Map
ConcurrentHashMap
- 高性能 Map,实现无锁或低锁分段操作。
- key 和 value 不能为 null。
ConcurrentSkipListMap
- 基于跳表(SkipList),key 有序,可替代 TreeMap。
- 插入/查询效率 O(log n),适用于高并发且需要排序的场景。
注意:
3. Set
- 实现类:
-
CopyOnWriteArraySet
:基于CopyOnWriteArrayList
ConcurrentSkipListSet
:基于ConcurrentSkipListMap
- 使用场景与对应的 List/Map 类似。
4. Queue(队列,最复杂)
维度一:是否阻塞
- 阻塞队列:
BlockingQueue
系列 - 非阻塞队列:
ConcurrentLinkedQueue
等
维度二:是否双端
- 单端:只能队尾入队、队首出队
- 双端:两端都可入队出队(
Deque
)
常见实现对照表:
分类 | 实现类 | 特点/用途 |
单端阻塞队列 |
| 有界,推荐使用 |
| 无缓冲,生产者必须等消费者 | |
| 带优先级的阻塞队列 | |
| 可延时出队 | |
| 混合阻塞/直接传递,性能优 | |
双端阻塞队列 |
| 支持两端操作 |
单端非阻塞队列 |
| 无界,高并发性能优 |
双端非阻塞队列 |
| 双端无锁队列 |
注意:
- 无界队列存在 OOM 风险,除
ArrayBlockingQueue
和LinkedBlockingQueue
其他均为无界。 - 实际应用中建议优先选择有界队列。
1.4. 总结与建议
选择容器的建议:
需求 | 推荐容器 |
多线程读多写少 |
|
高并发无序 Map |
|
高并发有序 Map |
|
高并发队列,双端支持 |
|
阻塞队列,生产者-消费者模型 |
|
阻塞队列且需优先级或延迟 |
|
常见“坑”:
- 组合操作不是原子(需手动同步)
- 同步容器遍历时需加锁
- CopyOnWrite 容器写入延迟,迭代器只读
- ConcurrentMap 的 key/value 不能为 null
- 无界队列可能 OOM
同步容器VS并发容器:
特性 | 同步容器(Synchronized) | 并发容器(Concurrent) |
引入版本 | JDK 1.2(如 | JDK 1.5 及以后 |
线程安全实现方式 | 所有方法使用 | 细粒度锁、CAS 等高效机制 |
性能 | 较差(所有操作都串行) | 更高性能(部分操作并发执行) |
适用场景 | 线程数量较少,竞争不激烈 | 高并发场景,要求更高性能 |
组合操作(如遍历+修改) | 手动加锁(如 | 内部支持更强的并发控制 |
常见类 |
|
, , 等 |
2. 原子类:无锁工具的典范
2.1. 为什么需要原子类?
以 count += 1
为例,它不是原子操作,因为涉及三个步骤:
- 从主内存读取 count;
- 执行加一操作;
- 写回主内存。
多个线程同时执行会出现线程安全问题。使用互斥锁可以解决,但代价较高(加锁/解锁+线程切换)。原子类就是为了解决这个问题的无锁方案。
AtomicLong 示例:如何用原子类解决问题?(无锁方案)
public class Test {AtomicLong count = new AtomicLong(0);void add10K() {int idx = 0;while(idx++ < 10000) {count.getAndIncrement();}}
}
count.getAndIncrement()
:原子操作,无需加锁,即可线程安全地自增。
2.2. 无锁方案的实现原理(使用原子类)
无锁的定义:
无锁编程是一种在并发程序中避免使用互斥锁(mutex)的并发控制方式。其目标是让线程在不阻塞的情况下共享数据,避免死锁、线程切换带来的开销等问题。
核心原理:CAS(Compare And Swap)
无锁算法的基础是CAS 指令,即“比较并交换”:
boolean CAS(V expectedValue, V newValue)
- 作为一条 CPU 指令,CAS 指令本身是能够保证原子性的。
执行逻辑:
- 比较当前内存中的值是否等于
expectedValue
- 如果相等,则用
newValue
更新该值,并返回true
- 如果不相等,说明被其他线程修改了,不做更新,返回
false
举例:自旋重试实现无锁自增
while (true) {int current = atomicValue.get();int next = current + 1;if (atomicValue.compareAndSet(current, next)) {break;}// 如果 CAS 失败则自旋重试
}
无锁的优势:
- 不阻塞,不会引起线程上下文切换
- 并发性能好,尤其在高并发下
无锁的挑战:
- 编码复杂
- 容易发生 ABA 问题(即 A → B → A,CAS 检查不出变更)
- 不适合临界区过大或逻辑复杂的场景
CAS 的经典问题:ABA 问题
什么是 ABA 问题?
- 假设线程 T1 读取 count 为 A;
- 线程 T2 把 count 改为 B;
- 线程 T3 又把 count 改回 A;
- T1 再次用 CAS 判断:值等于 A,CAS 成功;
- 但实际上,count 已经被修改过,这就是 ABA 问题。
如何解决?
通过引入版本号或标记:
AtomicStampedReference
:使用整型版本号;AtomicMarkableReference
:使用 boolean 标记。
2.3. 原子类的概念
什么是原子类(Atomic Class)?
原子类是 Java 并发包 java.util.concurrent.atomic
中提供的一组类,用于实现原子性操作的共享变量。常见如:
原子类 | 说明 |
| 原子性的 int 类型变量 |
| 原子性的 long 类型变量 |
| 原子引用类型变量 |
| 原子布尔类型变量 |
- 所有操作都是线程安全的
- 不依赖显式加锁(如
synchronized
) - 底层使用 CAS 保证原子性
2.4. 各语言原子操作示例
1. Python 原子操作示例
Python 的标准库中并没有直接的原子类(如 Java、Go),但可以借助 threading.Lock
或 multiprocessing.Value
、atomicwrites
,或使用第三方库 atomics
来实现。
示例(使用 threading.Lock
模拟原子操作):
import threadingclass AtomicInteger:def __init__(self, initial=0):self.value = initialself._lock = threading.Lock()def increment(self):with self._lock:self.value += 1return self.value# 测试
counter = AtomicInteger()def task():for _ in range(10000):counter.increment()threads = [threading.Thread(target=task) for _ in range(10)]for t in threads:t.start()
for t in threads:t.join()print("Final value:", counter.value)
- Python 的
GIL
限制了真正的并行性,但锁还是必需的,因为 I/O 或 C 扩展可能释放 GIL。
2. Java 原子操作示例
Java 原子类由 java.util.concurrent.atomic
包提供,最常用的是 AtomicInteger
。
示例:
import java.util.concurrent.atomic.AtomicInteger;public class AtomicExample {private static AtomicInteger counter = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {Runnable task = () -> {for (int i = 0; i < 10000; i++) {counter.incrementAndGet(); // 原子递增}};Thread[] threads = new Thread[10];for (int i = 0; i < 10; i++) {threads[i] = new Thread(task);threads[i].start();}for (Thread t : threads) {t.join();}System.out.println("Final value: " + counter.get());}
}
incrementAndGet()
方法底层使用 CAS 实现原子性。
- Go(Golang)原子操作示例
Go 提供了 sync/atomic
包用于原子操作。
示例:
package mainimport ("fmt""sync""sync/atomic"
)func main() {var counter int64 = 0var wg sync.WaitGroupfor i := 0; i < 10; i++ {wg.Add(1)go func() {for j := 0; j < 10000; j++ {atomic.AddInt64(&counter, 1) // 原子加法}wg.Done()}()}wg.Wait()fmt.Println("Final value:", counter)
}
atomic.AddInt64
是线程安全的,不需要额外加锁。
总结比较:
特性 | Python | Java | Go |
原子操作支持 | ❌(需自定义或第三方) | ✅ | ✅ |
原子加操作 | 自定义加锁实现 |
|
|
底层机制 | 依赖 GIL + Lock | CAS + volatile + Unsafe | CAS(基于硬件指令) |