浅析Collections.synchronizedList实现原理及如何做到线程安全、实现线程安全2种方式CopyOnWriteArrayList与Collections.synchronizedList的读写性能对比
一、Collections.synchronizedList 实现原理及如何做到线程安全
大家都知道ArrayList并不是线程安全的,如果想要做到线程安全,我们可以使用 Collections.synchronizedList, 但是使用 Collections.synchronizedList后是否真的就线程安全了?
1、Collections.synchronizedList 原理
我们先来看看Collections.synchronizedList 做了什么。
从源码来看,SynchronizedList 就是在 List的操作外包加了一层 synchronize 同步控制。
2、加了 Collections.synchronizedList 后,为什么还需要使用 synchronized ?
首先我们看官方文档,可以发现, 当用户通过迭代器遍历返回列表时,必须手动同步:
It is imperative that the user manually synchronize on the returned list when traversing it via [Iterator]
List list = Collections.synchronizedList(new ArrayList());
...
synchronized (list) {
Iterator i = list.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
}
也就是说官方文档明确提出:对于 使用 Iterator 遍历列表时,Collections.synchronizedList 可能发生错误。
那除了直接使用 Iterator 要加 synchronize 保证线程安全,还有什么情况会间接使用到 Iterator吗? 那就是 for each增强for循环;
在使用 Iteratior 遍历的同时,异步修改List的结构,发现抛出了 ConcurrentModificationException 异常;
那怎么解决呢?官方文档说的很清楚,我们在迭代器遍历返回列表时,增加手动同步处理,下面是IteratorRunnable 修改后 代码,仅仅是在外层加了 synchronized.
static class IteratorRunnable implements Runnable {
private List<Integer> list;
public IteratorRunnable(List<Integer> synchronizeList) {
this.list = synchronizeList;
}
@Override
public void run() {
while(true) {
synchronized (list) {
for (Integer i : list) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i + ",");
}
}
}
}
}
从运行结果来看,增加了synchronized 后,不会出现ConcurrentModificationException异常了。
3、探究下for each Java的实现
我们先来看看.class文件中for each
看到这,我们就可以确定 ,其实JAVA中的增强for循环底层是通过iterator来实现的。
二、CopyOnWriteArrayList与Collections.synchronizedList的性能对比
列表实现有 ArrayList、Vector、CopyOnWriteArrayList、Collections.synchronizedList(list) 四种方式。
1、ArrayList
ArrayList是非线性安全,此类的 iterator 和 listIterator 方法返回的迭代器是快速失败的:在创建迭代器之后,除非通过迭代器自身的 remove 或 add 方法从结构上对列表进行修改,否则在任何时间以任何方式对列表进行修改,迭代器都会抛出 ConcurrentModificationException。即在一方在遍历列表,而另一方在修改列表时,会报ConcurrentModificationException错误。而这不是唯一的并发时容易发生的错误,在多线程进行插入操作时,由于没有进行同步操作,容易丢失数据。
因此,在开发过程当中,ArrayList并不适用于多线程的操作。
2、Vector
从JDK1.0开始,Vector便存在JDK中,Vector是一个线程安全的列表,采用数组实现。其线程安全的实现方式是对所有操作都加上了synchronized关键字,这种方式严重影响效率,因此,不再推荐使用Vector了,Stackoverflow当中有这样的描述: Why is Java Vector class considered obsolete or deprecated?。 为什么 Java Vector(和 Stack)类被认为已过时或已弃用?
3、Collections.synchronizedList & CopyOnWriteArrayList
CopyOnWriteArrayList 和 Collections.synchronizedList 是实现线程安全的列表的两种方式。
两种实现方式分别针对不同情况有不同的性能表现,其中 CopyOnWriteArrayList的写操作性能较差,而多线程的读操作性能较好。
而Collections.synchronizedList的写操作性能比CopyOnWriteArrayList在多线程操作的情况下要好很多,而读操作因为是采用了synchronized关键字的方式,其读操作性能并不如CopyOnWriteArrayList。
因此在不同的应用场景下,应该选择不同的多线程安全实现类。
4、Collections.synchronizedList
Collections.synchronizedList的源码可知,其实现线程安全的方式是建立了list的包装类。
其中,SynchronizedList对部分操作加上了synchronized关键字以保证线程安全。但其iterator()操作还不是线程安全的。
5、CopyOnWriteArrayList
从字面可以知道,CopyOnWriteArrayList在线程对其进行些操作的时候,会拷贝一个新的数组以存放新的字段。
其没有加任何同步关键字,根据以上写操作的代码可知,其每次写操作都会进行一次数组复制操作,然后对新复制的数组进行些操作,不可能存在在同时又读写操作在同一个数组上( 不是同一个对象),而读操作并没有对数组修改,不会产生线程安全问题。
Java中两个不同的引用指向同一个对象,当第一个引用指向另外一个对象时,第二个引用还将保持原来的对象。
其中setArray()操作仅仅是对array进行引用赋值。Java中“=”操作只是将引用和某个对象关联,假如同时有一个线程将引用指向另外一个对象,一个线程获取这个引用指向的对象,那么他们之间不会发生ConcurrentModificationException,他们是在虚拟机层面阻塞的,而且速度非常快,是一个原子操作,几乎不需要CPU时间。
6、Collections.synchronizedList & CopyOnWriteArrayList在读写操作上的差距

(1)写操作:在线程数目增加时CopyOnWriteArrayList的写操作性能下降非常严重,而Collections.synchronizedList虽然有性能的降低,但下降并不明显。
(2)读操作:在多线程进行读时,Collections.synchronizedList和CopyOnWriteArrayList均有性能的降低,但是Collections.synchronizedList的性能降低更加显著。
结论:
CopyOnWriteArrayList,发生修改时候做 copy,新老版本分离,保证读的高性能,适用于以读为主,读操作远远大于写操作的场景中使用,比如缓存。
而Collections.synchronizedList则可以用在CopyOnWriteArrayList不适用,但是有需要同步列表的地方, 读写操作都比较均匀的地方。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2019-09-09 浅析Hooks的产生背景及在vue里使用hooks的意义