Java中普通集合如ArrayList、HashMap等都不是线程安全的,在多线程同时修改的场景下,可能会出现元素丢失、数据覆盖或者抛出ConcurrentModificationException异常的情况,因此需要采用特定的方式保证修改操作的安全性。

使用同步包装类实现线程安全修改
Java Collections工具类提供了同步包装方法,可以将普通集合包装成线程安全的集合,其核心原理是对集合的所有操作都加上同步锁,保证同一时间只有一个线程能修改集合。
常用的同步包装方法如下:
- Collections.synchronizedList:包装List集合
- Collections.synchronizedMap:包装Map集合
- Collections.synchronizedSet:包装Set集合
以下是使用同步List修改元素的示例代码:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SynchronizedCollectionDemo {
public static void main(String[] args) {
// 创建普通ArrayList并包装为同步List
List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());
// 创建10个线程同时向集合添加元素
for (int i = 0; i < 10; i++) {
int threadId = i;
new Thread(() -> {
for (int j = 0; j < 100; j++) {
syncList.add(threadId * 100 + j);
}
}).start();
}
// 等待所有线程执行完成
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出集合大小,结果应为1000
System.out.println("集合元素数量:" + syncList.size());
}
}
需要注意,同步包装类的迭代操作需要手动加锁,否则在迭代过程中如果有其他线程修改集合,依然会抛出并发修改异常。正确的迭代方式如下:
// 迭代同步List时需要手动加锁
synchronized (syncList) {
for (Integer num : syncList) {
System.out.println(num);
}
}
使用并发容器实现线程安全修改
Java并发包java.util.concurrent中提供了大量内置线程安全的并发容器,相比同步包装类,它们的性能通常更好,且支持更多并发场景下的操作。
常用的并发容器包括:
- CopyOnWriteArrayList:适合读多写少的List场景
- ConcurrentHashMap:线程安全的Map实现,性能优于同步Map
- ConcurrentLinkedQueue:线程安全的无界队列
以下是使用CopyOnWriteArrayList在多线程中修改集合的示例:
import java.util.concurrent.CopyOnWriteArrayList;
public class ConcurrentCollectionDemo {
public static void main(String[] args) {
// 创建并发List
CopyOnWriteArrayList<Integer> concurrentList = new CopyOnWriteArrayList<>();
// 创建10个线程同时添加元素
for (int i = 0; i < 10; i++) {
int threadId = i;
new Thread(() -> {
for (int j = 0; j < 100; j++) {
concurrentList.add(threadId * 100 + j);
}
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("并发List元素数量:" + concurrentList.size());
}
}
CopyOnWriteArrayList的写操作会复制整个底层数组,因此写性能较差,但读操作不需要加锁,适合读多写少的场景。而ConcurrentHashMap采用分段锁或CAS机制实现线程安全,读写性能都比较优异,是多线程场景下Map的首选实现。
手动加锁控制集合修改
如果需要在修改集合时执行额外的逻辑,或者需要更细粒度的锁控制,也可以手动使用synchronized关键字或者ReentrantLock来加锁,保证修改操作的原子性。
以下是使用ReentrantLock手动加锁修改集合的示例:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
public class ManualLockDemo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
ReentrantLock lock = new ReentrantLock();
for (int i = 0; i < 10; i++) {
int threadId = i;
new Thread(() -> {
for (int j = 0; j < 100; j++) {
lock.lock();
try {
// 加锁后执行修改操作,还可以添加其他自定义逻辑
list.add(threadId * 100 + j);
// 比如打印当前集合大小
System.out.println("当前线程添加后集合大小:" + list.size());
} finally {
lock.unlock();
}
}
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终集合元素数量:" + list.size());
}
}
不同方案对比
以下是几种线程安全修改集合方案的对比:
| 方案 | 适用场景 | 性能 | 注意事项 |
|---|---|---|---|
| 同步包装类 | 简单的多线程修改场景,不需要复杂并发操作 | 一般,所有操作都加全局锁 | 迭代需要手动加锁 |
| 并发容器 | 高并发场景,尤其是读多写少或Map操作 | 较好,采用更高效的并发控制机制 | 不同容器特性不同,需根据场景选择 |
| 手动加锁 | 需要自定义修改逻辑,或细粒度锁控制 | 取决于锁的实现和使用方式 | 需要注意锁的释放,避免死锁 |
注意事项
无论使用哪种方案,都需要注意以下几点:
- 不要对线程安全的集合进行非原子性的复合操作,比如先判断集合是否存在元素再添加,这类操作依然需要额外加锁保证原子性
- 根据业务场景选择合适的方案,读多写少优先选CopyOnWriteArrayList,Map操作优先选ConcurrentHashMap
- 避免将普通集合直接暴露给多线程修改,即使初期是单线程使用,后续扩展也可能引发并发问题
Java多线程集合线程安全synchronized修改时间:2026-07-05 12:09:25