1、Java在JDK1.5之前基本上对所有集合都实现了线程同步版本synchronized*,用集合工具类Collections即可得到,如下都为Collections中的方法:
static <T> Collections<T>
synchronizedCollection(Collection<T> c)
返回指定 collection 支持的同步(线程安全的)collection。
static <T> List<T>
synchronizedList(List<T> list)
返回指定列表支持的同步(线程安全的)列表。
static <K,V> Map<K,V>
synchronizedMap(Map<K,V> m)
返回由指定映射支持的同步(线程安全的)映射。
static <T> Set<T>
synchronizedSet(Set<T> s)
返回指定 set 支持的同步(线程安全的)set。
static <K,V> SortedMap<K,V>
synchronizedSortedMap(SortedMap<K,V> m)
返回指定有序映射支持的同步(线程安全的)有序映射。
static <T> SortedSet<T>
synchronizedSortedSet(SortedSet<T> s)
返回指定有序 set 支持的同步(线程安全的)有序 set。
其内部实现是在内部维护一个对应的集合类型,然后相应所有的操作都加上同步关键字synchronized,同步方法内再调用内部集合的方法,如下为synchronizedMap的实现:
private static class SynchronizedMap<K,V>
implements Map<K,V>, Serializable {
private final Map<K,V> m; // Backing Map
final Object mutex;
SynchronizedMap(Map<K,V> m) {
if (m==null)
throw new NullPointerException();
this.m = m;
mutex = this;
}
public V get(Object key) {
synchronized(mutex) {return m.get(key);}
}
public V put(K key, V value) {
synchronized(mutex) {return m.put(key, value);}
}
......
}
2、传统方式下的Collection在迭代集合时,不允许对集合进行修改,如下代码会抛出ConcurrentModificationException异常:
Collection<String> users = new ArrayList<String>();
users.add("first");
users.add("second");
users.add("third");
Iterator<String> itrUsers = users.iterator();
while (itrUsers.hasNext()) {
String user = itrUsers.next();
if ("first".equals(user)) {
users.remove(user);
}
System.out.println(user);
}
在调用next()方法时,由于删除了元素导致期望长度和实现长度不一至而抛出异常:
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
把上面ArrayList换成CopyOnWriteArrayList即可完成在迭代中对集合进行操作,并且在多线程中也是安全的:
Collection<String> users = new CopyOnWriteArrayList<String>();
3、CopyOnWriteArrayList是在Java JDK1.5中引入的并发类,在java.util.concurrent包下,它是ArrayList 的一个线程安全的变体,并且是在读时无锁的ArrayList:
public E get(int index) {
return (E)(getArray()[index]);
}
此方法很简单没有加锁,有可能会出现脏读的情况,但是性能非常高,对于写少读多且对脏数据要求不严的场景可以使用。
构造方法:
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
与ArrayList不同,CopyOnWriteArrayList创建一个大小为0的数组。
add(E)方法:add方法并没有使用内置锁,而是使用JDK1.5提供的显示锁ReentrantLock来保证线程的安全的:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
此处和ArrayList不同的是每次都创建一个新的Object数组,此数据的大小为当前数据大小加1,将之前的数组中的内容复制到新的数组中,并将新增加的对象放入到数组的末尾,最后把新数组的引用赋值给全局的数组对象:
final void setArray(Object[] a) {
array = a;
}
remove(Object)方法:和add方法一样,删除元素方法也采用JDK1.5的加锁方式来保证线程安全,但它和ArrayList采用的删除方法不同,它是创建一个长度比当前数组小1的新数组,然后遍历老数组,其它元素全部加到新数组,最后把新数组的引用赋值给全局的数组对象,它并没有使用System的arrayCopy来实现,可能会导致性能一定的下降。
iterator()迭代:调用iterator方法后创建一个新的COWIterator实例,并保存了一个当前数组的快照,在调用next遍历时仅对快照进行遍历,所以在迭代CopyOnWriteArrayList中操作其中元素不会像ArrayList一样抛出ConcurrentModificationException异常:
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
4、和ArrayList性能比较
单线程:在元素较少的情况下,两个类的性能基本上一至,但是到元素很多时,CopyOnWriteArrayList增加元素的删除元素性能会差一点
多线程:随着元素数量和线程数量的增加,CopyOnWriteArrayList在增加和删除元素的性能就会下降,而且比ArrayList性能低。但在查找元素时随着元素数量和线程数量的增加性能比ArrayList好。
在读多写少的并发场景中,CopyOnWriteArrayList比ArrayList是更好的选择。