1、基本认识
简单来讲,我们平时经常使用的ArrayList不是线程安全的。我们通过使用 Collections.synchronizedList 来包装一个线程安全的ArrayList。
我们一般创建一个普通的list是这样的姿势,
List<String> list = new ArrayList<>();
// populate the list
list.add("apple");
list.add("orange");
list.add("banana");
然后我们把这个list变成线程安全的list,操作的姿势是这样的,
List<String> syncList = Collections.synchronizedList(list);
这两个list在单线程下使用没有任何不同。
Collections.synchronizedList
2、使用场景
我们来看一个多线程访问list的示例,
public class SynchronizedListTest {
List<String> list = new ArrayList<>();
List<String> syncList = Collections.synchronizedList(list);
@Test
public void test1() {
list.add("apple");
list.add("orange");
list.add("banana");
System.out.println("sync list:" + syncList);
Thread t1 = new FruitManager();
Thread t2 = new FruitManager();
t1.start();
t2.start();
}
class FruitManager extends Thread {
public void run() {
System.out.println("thread - " + Thread.currentThread().getName() + " start");
if (syncList.size() > 0) {
String fruit = (String) syncList.remove(0);
System.out.println("fruit:" + fruit);
}
}
}
很明显,这种场景如果不使用线程安全的集合,就会造成集合中的元素被重复remove的情况,从而导致程序出错。
3、源码剖析
好的程序员不光要知其然,还要知其所以然。如果只是停留在会用阶段,你永远都只是一个初级的程序员。
Collections.synchronizedList 源码也比较简单,就是给所有的操作就加上了 synchroinzed 锁。这里截取部分代码,
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
private static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
SynchronizedList(List<E> 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 ListIterator<E> listIterator() {
return list.listIterator(); // Must be manually synched by user
}
public ListIterator<E> listIterator(int index) {
return list.listIterator(index); // Must be manually synched by user
}
}
这里的 mutex 则是父类 SynchronizedCollection 中的this对象。
细心的读者可能已经看到了,好像迭代器没有加同步锁!是的,也就是说虽然list是安全的,但是list的迭代器并不是线程安全的。
所以官方建议使用的迭代器的时候,要用如下的姿势,
List list = Collections.synchronizedList(new ArrayList());
...
synchronized (list) {
Iterator i = list.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
}
4、总结
- SynchronizedList可以将List转成线程安全的集合,在多线程环境下建议使用。
- 使用迭代器遍历时,即使用了 SynchronizedList 也要手动进行同步处理。