Java 集合,也叫做容器,主要由两大接口派生而来: Collection
接口(存放单一元素)、 Map
接口(存放键值对)。对于Collection
接口,有三个主要的子接口:List
、Set
和 Queue
。有关内容学习可以看 JavaGuide(Java 面试+学习指南)中 Java 集合这一章节
ArrayList线程不安全
我们都知道 ArrayList
是线程不安全的,究其原因是因为其在进行写操作的时候,并没有对并发条件下进行相应的处理,我们可以进入 ArrayList
中查看 add()
方法的实现(如下),从中可以看出未作任何处理,未在方法上加 synchronized
修饰

下面我们可以通过一个实例来证明它是线程不安全的:
/**
* 线程不安全举例
*/
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 30; i++){
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
当我们运行这个代码时,会出现错误 java.util.ConcurrentModificationException
,并发修改错误
解决方法
- 方案一:Vector
第一种方法,就是不用 ArrayList
这种不安全的 List
实现类,而采用 Vector
,其是线程安全的
关于 Vector
如何实现线程安全的,而是在方法上加了锁,即 synchronized
。这样每次只能有一个线程进行操作,不会出现线程不安全的问题,但因为加锁的缘故,并发性能下降了

- 方案二:
Collections.synchronized()
List<String> list = Collections.synchronizedList(new ArrayList<>());
采用 Collections
集合工具类,在 ArrayList
外面包装一层同步机制
- 方案三:采用 JUC 里面的方法
采用 CopyOnWriteArrayList
:写时复制,采用了一种读写分离的思想
List<String> list = new CopyOnWriteArrayList<>();
下面我们查看底层 add()
方法源码来体验写时复制

所谓写时复制简单来说,就是平时查询的时候,都不需要加锁,随意访问,只有在更新的时候,才会加锁并把原来的数据复制出一个副本,然后修改这个副本,最后把原数据替换成当前的副本。值得注意的是写时复制修改操作的同时,读操作不会被阻塞,而是继续读取旧的数据。这点要跟读写锁区分一下
写时复制的优缺点
优点:对于一些读多写少的操作,写入时复制的做法就很不错。同时 CopyOnWriteArrayList
并发安全且性能比 Vector
好,原因是Vector
增删改查方法都加了 synchronized
来保证同步,因此每个方法执行的时候都要去获得锁,性能大大下降,而 CopyOnWriteArrayList
只是在增删改上加锁,读不加锁,因此性能好于 Vector
缺点:数据一致性问题。这种实现只是保证数据最终的一致性,在添加到拷贝数据而还没进行替换的时候,读到的仍然是旧数据。此外还有内存占用问题。如果对象比较大,频繁地进行替换会消耗内存,从而引发 Java 的 GC 问题
HashSet线程不安全
如果查看源码就会发现 HashSet
是用 HashMap
来实现的,只不过 HashSet
只是用 HashMap
的 key 来进行存储,而 value 存储的是一个 Object 类型的常量
解决方法
- 方案一:
Collections.synchronized()
Set<String> set = Collections.synchronizedSet(new HashSet<>());
- 方案二:采用 JUC 里面的方法
Set<String> set = new CopyOnWriteArraySet<>();
查看 CopyOnWriteArraySet
源码会发现它的底层使用了 CopyOnWriteArrayList

HashMap线程不安全
这里简要说一下 HashMap
的数据结构(JDK1.8 以后):数组 + 链表 + 红黑树,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树),以减少搜索时间
HashMap
的源码讲解可以看这篇文章:HashMap源码&底层数据结构分析
解决方法
- 方案一:
Collections.synchronized()
Map<Integer, String> map = Collections.synchronizedMap(new HashMap<>());
- 方案二:采用 JUC 里面的方法
Map<Integer, String> map = new ConcurrentHashMap<>();
参考
- 尚硅谷 Java 大厂面试题第 2 季
- JavaGuide(Java面试+学习指南)