# 23. 如何使用 Collections.synchronizedList()
确保线程安全?它的缺陷是什么?
# 标准答案
Collections.synchronizedList(List<T> list)
返回的是线程安全的 List,通过在所有方法上加 synchronized
来保证并发安全。但它的缺陷是:
- 同步粒度较大,所有操作都串行化,并发性能较低。
- 迭代器遍历仍然不安全,需要手动同步
synchronized(list) {}
保护迭代过程,否则可能抛ConcurrentModificationException
。 - 适用于低并发场景,高并发下建议使用
CopyOnWriteArrayList
或ConcurrentLinkedQueue
。
# 答案解析
# 1. Collections.synchronizedList()
是如何实现线程安全的?
该方法的实现方式是在 List
的所有方法(add()、remove()、get()
等)上加 synchronized
关键字,从而确保单线程访问:
List<Integer> list = Collections.synchronizedList(new ArrayList<>());
list.add(1);
list.add(2);
1
2
3
2
3
查看 JDK 8 源码:
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
static class SynchronizedList<E> extends SynchronizedCollection<E> implements List<E> {
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
public synchronized boolean add(E e) {
return list.add(e);
}
public synchronized E get(int index) {
return list.get(index);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
可以看到,所有方法都加了 synchronized
,因此所有线程访问 synchronizedList
的方法时,必须获取锁,避免并发问题。
# 2. 线程安全的缺陷
# 缺陷 1:遍历时仍然不安全
即使 synchronizedList
确保了单线程访问,但迭代过程中,多个线程仍然可能修改 List
,导致 ConcurrentModificationException
:
List<Integer> list = Collections.synchronizedList(new ArrayList<>());
list.add(1);
list.add(2);
for (Integer num : list) { // 线程不安全
list.remove(num); // ConcurrentModificationException
}
1
2
3
4
5
6
2
3
4
5
6
正确方式:手动加锁
synchronized (list) {
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
it.next();
it.remove();
}
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 缺陷 2:锁粒度大,影响并发性能
由于 synchronizedList
的所有操作都串行执行,导致并发性能差,适合低并发场景。
高并发时,建议用 CopyOnWriteArrayList
,它读操作无锁,写操作复制数组,适用于读多写少的场景:
List<Integer> list = new CopyOnWriteArrayList<>();
list.add(1);
list.add(2);
1
2
3
2
3
# 缺陷 3:可能导致死锁
如果多个线程交叉调用 synchronizedList
,并在遍历过程中使用 synchronized
,可能导致锁重入死锁问题。
# 最佳实践
- 遍历时用
synchronized(list) {}
保护迭代器,避免ConcurrentModificationException
。 - 高并发场景用
CopyOnWriteArrayList
,避免synchronized
导致的性能问题。 - 避免嵌套同步锁,防止死锁。
# 深入追问
synchronizedList()
和CopyOnWriteArrayList
的核心区别是什么?Collections.synchronizedMap()
是否也存在遍历安全问题?ConcurrentModificationException
是如何检测到并发修改的?
# 相关面试题
synchronizedList()
vsCopyOnWriteArrayList
的应用场景?Collections.synchronizedMap()
和ConcurrentHashMap
有何区别?- 为什么
synchronizedList()
不能完全解决并发问题?