# 23. 如何使用 Collections.synchronizedList() 确保线程安全?它的缺陷是什么?

# 标准答案

Collections.synchronizedList(List<T> list) 返回的是线程安全的 List,通过在所有方法上加 synchronized 来保证并发安全。但它的缺陷是:

  1. 同步粒度较大,所有操作都串行化,并发性能较低
  2. 迭代器遍历仍然不安全,需要手动同步 synchronized(list) {} 保护迭代过程,否则可能抛 ConcurrentModificationException
  3. 适用于低并发场景,高并发下建议使用 CopyOnWriteArrayListConcurrentLinkedQueue

# 答案解析

# 1. Collections.synchronizedList() 是如何实现线程安全的?

该方法的实现方式是在 List 的所有方法(add()、remove()、get() 等)上加 synchronized 关键字,从而确保单线程访问

List<Integer> list = Collections.synchronizedList(new ArrayList<>());
list.add(1);
list.add(2);
1
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

可以看到,所有方法都加了 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

正确方式:手动加锁

synchronized (list) { 
    Iterator<Integer> it = list.iterator();
    while (it.hasNext()) {
        it.next();
        it.remove();
    }
}
1
2
3
4
5
6
7
# 缺陷 2:锁粒度大,影响并发性能

由于 synchronizedList 的所有操作都串行执行,导致并发性能差,适合低并发场景。
高并发时,建议用 CopyOnWriteArrayList,它读操作无锁,写操作复制数组,适用于读多写少的场景:

List<Integer> list = new CopyOnWriteArrayList<>();
list.add(1);
list.add(2);
1
2
3
# 缺陷 3:可能导致死锁

如果多个线程交叉调用 synchronizedList,并在遍历过程中使用 synchronized,可能导致锁重入死锁问题。

# 最佳实践

  1. 遍历时用 synchronized(list) {} 保护迭代器,避免 ConcurrentModificationException
  2. 高并发场景用 CopyOnWriteArrayList,避免 synchronized 导致的性能问题。
  3. 避免嵌套同步锁,防止死锁。

# 深入追问

  • synchronizedList()CopyOnWriteArrayList 的核心区别是什么?
  • Collections.synchronizedMap() 是否也存在遍历安全问题?
  • ConcurrentModificationException 是如何检测到并发修改的?

# 相关面试题

  • synchronizedList() vs CopyOnWriteArrayList 的应用场景?
  • Collections.synchronizedMap()ConcurrentHashMap 有何区别?
  • 为什么 synchronizedList() 不能完全解决并发问题?