一、synchronizedList
的线程安全实现逻辑
- 包装类选择
调用Collections.synchronizedList(List<T> list)
时,会根据传入List
是否实现RandomAccess
接口(如ArrayList
实现该接口,LinkedList
不实现),返回不同的包装类:
- 实现
RandomAccess
:返回SynchronizedRandomAccessList
(继承自SynchronizedList
); - 未实现:返回
SynchronizedList
。
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
- 核心锁机制
线程安全的核心是 对象锁(mutex
)+synchronized
同步块:
SynchronizedList
继承自SynchronizedCollection
,在父类中初始化mutex
锁对象(mutex = this
,即包装类实例本身);- 对
get
、set
、add
、remove
等所有修改/查询方法,均通过synchronized (mutex)
包裹,确保同一时刻只有一个线程能执行这些方法。
static class SynchronizedCollection<E> implements Collection<E>, Serializable {
private static final long serialVersionUID = 3053995032091335093L;
final Collection<E> c; // Backing Collection
final Object mutex; // Object on which to synchronize
SynchronizedCollection(Collection<E> c) {
this.c = Objects.requireNonNull(c);
mutex = this;
}
SynchronizedCollection(Collection<E> c, Object mutex) {
this.c = Objects.requireNonNull(c);
this.mutex = Objects.requireNonNull(mutex);
}
public int size() {
synchronized (mutex) {return c.size();}
}
public boolean isEmpty() {
synchronized (mutex) {return c.isEmpty();}
}
public boolean contains(Object o) {
synchronized (mutex) {return c.contains(o);}
}
public Object[] toArray() {
synchronized (mutex) {return c.toArray();}
}
public <T> T[] toArray(T[] a) {
synchronized (mutex) {return c.toArray(a);}
}
public Iterator<E> iterator() {
return c.iterator(); // Must be manually synched by user!
}
public boolean add(E e) {
synchronized (mutex) {return c.add(e);}
}
public boolean remove(Object o) {
synchronized (mutex) {return c.remove(o);}
}
public boolean containsAll(Collection<?> coll) {
synchronized (mutex) {return c.containsAll(coll);}
}
public boolean addAll(Collection<? extends E> coll) {
synchronized (mutex) {return c.addAll(coll);}
}
public boolean removeAll(Collection<?> coll) {
synchronized (mutex) {return c.removeAll(coll);}
}
public boolean retainAll(Collection<?> coll) {
synchronized (mutex) {return c.retainAll(coll);}
}
public void clear() {
synchronized (mutex) {c.clear();}
}
public String toString() {
synchronized (mutex) {return c.toString();}
}
// Override default methods in Collection
@Override
public void forEach(Consumer<? super E> consumer) {
synchronized (mutex) {c.forEach(consumer);}
}
@Override
public boolean removeIf(Predicate<? super E> filter) {
synchronized (mutex) {return c.removeIf(filter);}
}
@Override
public Spliterator<E> spliterator() {
return c.spliterator(); // Must be manually synched by user!
}
@Override
public Stream<E> stream() {
return c.stream(); // Must be manually synched by user!
}
@Override
public Stream<E> parallelStream() {
return c.parallelStream(); // Must be manually synched by user!
}
private void writeObject(ObjectOutputStream s) throws IOException {
synchronized (mutex) {s.defaultWriteObject();}
}
}
二、效率低下的原因
SynchronizedList
采用 “粗粒度锁” 实现:
所有方法共用同一把 mutex
锁,无论操作是读(如 get
)还是写(如 add
),都会独占锁。这意味着即使多个线程仅执行读操作,也需要排队等待锁释放,无法实现“读-读并发”,导致并发效率显著低于 CopyOnWriteArrayList
等细粒度锁/无锁实现。
三、遍历需额外加锁的原因
SynchronizedList
内部的锁仅保证 单个方法调用的原子性,但无法保证 多步操作(如遍历)的原子性:
- 其
iterator()
方法直接返回底层List
的迭代器(未加锁),迭代器本身不具备线程安全性; - 若不加外部锁,可能出现“并发修改异常”(如线程 A 遍历
hasNext()
时存在元素,线程 B 立即删除该元素,线程 A 调用next()
时会报错)。
因此,官方要求遍历 SynchronizedList
时,需在外部用 synchronized (list)
包裹迭代逻辑,确保遍历全程独占锁,避免并发修改问题。
public Iterator<E> iterator() {
return c.iterator(); // Must be manually synched by user!
}
示例代码如下:
List list = Collections.synchronizedList(new ArrayList());
// 正确遍历方式
synchronized (list) {
Iterator i = list.iterator();
while (i.hasNext()) {
foo(i.next());
}
}