java集合工具类Collections.synchronizedList提供了集合的线程安全包装方法。
那么它是如何让一个集合变成线程安全的呢?为什么说这种线程安全集合的实现效率非常低下?
synchronizedList的实现
我们来看源码吧。
public static List synchronizedList(List list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
/**
* @serial include
*/
static class SynchronizedList
extends SynchronizedCollection
implements List {
private static final long serialVersionUID = -7754090372962971524L;
final List list;
SynchronizedList(List list) {
super(list);
this.list = list;
}
SynchronizedList(List list, Object mutex) {
super(list, mutex);
this.list = list;
}
public boolean equals(Object o) {
if (this == o)
return true;
synchronized (mutex) {return list.equals(o);}
}
public int hashCode() {
synchronized (mutex) {return list.hashCode();}
}
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
public int indexOf(Object o) {
synchronized (mutex) {return list.indexOf(o);}
}
public int lastIndexOf(Object o) {
synchronized (mutex) {return list.lastIndexOf(o);}
}
public boolean addAll(int index, Collection extends E> c) {
synchronized (mutex) {return list.addAll(index, c);}
}
public ListIterator listIterator() {
return list.listIterator(); // Must be manually synched by user
}
public ListIterator listIterator(int index) {
return list.listIterator(index); // Must be manually synched by user
}
public List subList(int fromIndex, int toIndex) {
synchronized (mutex) {
return new SynchronizedList<>(list.subList(fromIndex, toIndex),
mutex);
}
}
@Override
public void replaceAll(UnaryOperator operator) {
synchronized (mutex) {list.replaceAll(operator);}
}
@Override
public void sort(Comparator super E> c) {
synchronized (mutex) {list.sort(c);}
}
/**
* SynchronizedRandomAccessList instances are serialized as
* SynchronizedList instances to allow them to be deserialized
* in pre-1.4 JREs (which do not have SynchronizedRandomAccessList).
* This method inverts the transformation. As a beneficial
* side-effect, it also grafts the RandomAccess marker onto
* SynchronizedList instances that were serialized in pre-1.4 JREs.
*
* Note: Unfortunately, SynchronizedRandomAccessList instances
* serialized in 1.4.1 and deserialized in 1.4 will become
* SynchronizedList instances, as this method was missing in 1.4.
*/
private Object readResolve() {
return (list instanceof RandomAccess
? new SynchronizedRandomAccessList<>(list)
: this);
}
}
当synchronizedList传入的参数类型是ArrayList时, 因为ArrayList实现了RandomAccess接口,所以synchronizedList会构建一个SynchronizedRandomAccessList对象,不过没关系,SynchronizedList是SynchronizedRandomAccessList的父类,我们直接看他的实现。
list 对象直接维护了传递进来的参数List类型参数
而在get set add remove等方法中的实现都用线程同步语句块 synchronized (mutex)
封装起来。那么mutex这把锁是谁呢? 看起来要到super(list);里面去找了.这时候就来到了SynchronizedList的父类SynchronizedCollection
/**
* @serial include
*/
static class SynchronizedCollection implements Collection, Serializable {
private static final long serialVersionUID = 3053995032091335093L;
final Collection c; // Backing Collection
final Object mutex; // Object on which to synchronize
SynchronizedCollection(Collection c) {
this.c = Objects.requireNonNull(c);
mutex = this;
}
SynchronizedCollection(Collection 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[] toArray(T[] a) {
synchronized (mutex) {return c.toArray(a);}
}
public Iterator 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 spliterator() {
return c.spliterator(); // Must be manually synched by user!
}
@Override
public Stream stream() {
return c.stream(); // Must be manually synched by user!
}
@Override
public Stream parallelStream() {
return c.parallelStream(); // Must be manually synched by user!
}
private void writeObject(ObjectOutputStream s) throws IOException {
synchronized (mutex) {s.defaultWriteObject();}
}
}
我们清楚的看到 mutex = this; 这个锁就是对象自己!
通过上面的源码我们可以知道了,synchronizedList实现线程安全的方法就是对自己暴力加锁,这效率能不低下吗?
在获取安全的list后遍历时,外层为何还要用synchronized同步?
官方文档就是如此使用synchronizedList的:
List list = Collections.synchronizedList(new ArrayList());
...
synchronized (list) {
Iterator i = list.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
}
问题来了:既然封装类内部已经加了对象锁,为什么外部还要加一层对象锁?
先看下官方文档的解释吧:
# Understanding Collections and Thread Safety in Java
NOTE:
When using the iterator of a synchronized collection, we should use synchronized block to safeguard the iteration code because the iterator itself is not thread-safe. Consider the following code:
List safeList = Collections.synchronizedList(new ArrayList<>());
// adds some elements to the list
Iterator iterator = safeList.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
System.out.println(next);
}
Although the safeList is thread-safe, its iterator is not, so we should manually add synchronized block like this:
synchronized (safeList) {
while (iterator.hasNext()) {
String next = iterator.next();
System.out.println(next);
}
}
其实,这个迭代的外部锁其实就是为了list.iterator()在读取过程中,不会本来hasNext()有的,但在调用i.next()的时候,另外一个线程把它删了,这个synchronized块是为了保障这三行代码在多个线程里同时执行的并发问题。
至于synchronizedList的内部锁,那是在并发执行add/remove的时候,不要把多个线程的东西加到list内部实现的同一个位置上去,导致数据丢失或者脏数据等问题,这是为了保证这个List在执行add/remove时不会存在并发问题。
简而言之,这两个锁是不同层面上的并发问题。
所以,当我们对synchronizedList进行遍历的时候一定不要忘了,在外部也加上synchronized(list),以保证线程安全。