当前位置: 首页 > java >正文

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

常见实现对照表:

分类

实现类

特点/用途

单端阻塞队列

ArrayBlockingQueue, LinkedBlockingQueue

有界,推荐使用

SynchronousQueue

无缓冲,生产者必须等消费者

PriorityBlockingQueue

带优先级的阻塞队列

DelayQueue

可延时出队

LinkedTransferQueue

混合阻塞/直接传递,性能优

双端阻塞队列

LinkedBlockingDeque

支持两端操作

单端非阻塞队列

ConcurrentLinkedQueue

无界,高并发性能优

双端非阻塞队列

ConcurrentLinkedDeque

双端无锁队列

注意:

  • 无界队列存在 OOM 风险,除 ArrayBlockingQueueLinkedBlockingQueue 其他均为无界。
  • 实际应用中建议优先选择有界队列

1.4. 总结与建议

选择容器的建议:

需求

推荐容器

多线程读多写少

CopyOnWriteArrayList

高并发无序 Map

ConcurrentHashMap

高并发有序 Map

ConcurrentSkipListMap

高并发队列,双端支持

ConcurrentLinkedDeque

阻塞队列,生产者-消费者模型

LinkedBlockingQueueArrayBlockingQueue

阻塞队列且需优先级或延迟

PriorityBlockingQueue / DelayQueue

常见“坑”:

  • 组合操作不是原子(需手动同步)
  • 同步容器遍历时需加锁
  • CopyOnWrite 容器写入延迟,迭代器只读
  • ConcurrentMap 的 key/value 不能为 null
  • 无界队列可能 OOM

同步容器VS并发容器:

特性

同步容器(Synchronized)

并发容器(Concurrent)

引入版本

JDK 1.2(如 Collections.synchronizedXXX

JDK 1.5 及以后

线程安全实现方式

所有方法使用 synchronized实现同步

细粒度锁、CAS 等高效机制

性能

较差(所有操作都串行)

更高性能(部分操作并发执行)

适用场景

线程数量较少,竞争不激烈

高并发场景,要求更高性能

组合操作(如遍历+修改)

手动加锁(如 synchronized(list)

内部支持更强的并发控制

常见类

Vector, Hashtable, Collections.synchronizedList

ConcurrentHashMap

, CopyOnWriteArrayList

, ConcurrentLinkedQueue

2. 原子类:无锁工具的典范

2.1. 为什么需要原子类?

count += 1 为例,它不是原子操作,因为涉及三个步骤:

  1. 从主内存读取 count;
  2. 执行加一操作;
  3. 写回主内存。

多个线程同时执行会出现线程安全问题。使用互斥锁可以解决,但代价较高(加锁/解锁+线程切换)。原子类就是为了解决这个问题的无锁方案

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 中提供的一组类,用于实现原子性操作的共享变量。常见如:

原子类

说明

AtomicInteger

原子性的 int 类型变量

AtomicLong

原子性的 long 类型变量

AtomicReference<T>

原子引用类型变量

AtomicBoolean

原子布尔类型变量

  • 所有操作都是线程安全的
  • 不依赖显式加锁(如 synchronized
  • 底层使用 CAS 保证原子性

2.4. 各语言原子操作示例

1. Python 原子操作示例

Python 的标准库中并没有直接的原子类(如 Java、Go),但可以借助 threading.Lockmultiprocessing.Valueatomicwrites,或使用第三方库 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 实现原子性。
  1. 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

原子操作支持

❌(需自定义或第三方)

AtomicInteger

sync/atomic

原子加操作

自定义加锁实现

incrementAndGet()

atomic.AddInt64()

底层机制

依赖 GIL + Lock

CAS + volatile + Unsafe

CAS(基于硬件指令)

http://www.xdnf.cn/news/12513.html

相关文章:

  • CppCon 2015 学习:How to Make Your Data Structures Wait-Free for Reads
  • FPGA没有使用的IO悬空对漏电流有没有影响
  • 什么是质量管理工具?质量管理工具有哪些优势?
  • C#中datagridview单元格value为{}大括号
  • C++优选算法 438. 找到字符串中所有字母异位词
  • 【Dv3Admin】系统视图菜单按钮管理API文件解析
  • CodeTop100 Day24
  • 【UEFI系列】SEC阶段讲解
  • 2024年第十五届蓝桥杯青少Scratch初级组-国赛—画矩形
  • Python-15(类与对象)
  • 人工智能初学者可以从事哪些岗位?
  • 逻辑卷和硬盘配额(补充)
  • 会计 - 合并1- 业务、控制、合并日
  • 6个月Python学习计划 Day 16 - 迭代器、生成器表达式、装饰器入门
  • 【汇编逆向系列】八、函数调用包含混合参数-8种参数传参,条件跳转指令,转型指令,movaps 16字节指令
  • 第16届蓝桥杯青少Scratch 4月stema——飞翔的小燕子
  • 二叉树基础全解:存储方式、遍历原理与查找树对比
  • Go垃圾回收参数调优:实现低延迟服务的实战指南
  • MongoDB检查慢查询db.system.profile.find 分析各参数的作用
  • 一篇文章实现Android图片拼接并保存至相册
  • 4082N信号频谱分析仪
  • 设置应用程序图标
  • Android设备推送traceroute命令进行网络诊断
  • 晨控CK-FR102ANS与欧姆龙NX系列PLC配置EtherNet/IP通讯配置操作手册
  • 96.如何使用C#实现串口发送? C#例子
  • 数据结构与算法——二叉树高频题目(1)
  • Oracle数据库学习笔记 - 创建、备份和恢复
  • html表格转换为markdown
  • 测试设计技术全解析:黑盒与白盒测试的七种武器与覆盖率指标
  • 深入解析Java中的装箱与拆箱机制