一、概述
传统方式下的Collection在迭代集合时,不允许对集合进行修改。
传统方式下用Collections工具类提供的synchronizedCollection方法来获得同步集合。分析该方法的实现源码:其实就是把方法放到同步代码块中,锁为当前集合对象。 Java5中提供了如下一些同步集合类:
通过查看java.util.concurrent包下的介绍可以知道有哪些并发集合,如:ConcurrentHashMap 、CopyOnWriteArrayList 、CopyOnWriteArraySet。
二、多线程中的集合问题
场景:有一个集合,对该集合采用迭代器进行遍历之后,又对其添加了一个元素。
我们分别使用Arralist、Vector、Collections.synchronizedList、Collections.synchronizedList使用synchronized关键字进行同步控制、使用java.util.concurrent包中的高效的同步集合ConcurrentLinkedQueue进行测试。 首先创建一个任务,该任务遍历集合,又对其添加了一个元素。
public class ModifyCollectionTask implements Runnable {
Collection list;
Lock lock = null;
public ModifyCollectionTask(Collection slist, Lock lock) {
this.list = slist;
this.lock = lock;
}
public void run() {
//lock.lock();
//try{
// 遍历列表
for (Integer num : list) {
System.out.println("线程"+Thread.currentThread().getName()+"数据为:"+num);
}
// 向列表添加元素
list.add(30);
//}finally{lock.unlock();}
}
}
1. 使用ArraList集合
public class MultiThreadListTest {
public static void main(String[] args) {
Collection list = new ArrayList();
list.add(6);
list.add(3);
list.add(43);
list.add(88);
list.add(1);
Lock lock = new ReentrantLock();
//启动100个线程,在多线程环境下,测试集合的同步问题
for (int i = 0; i < 100; i++) {
new Thread(new ModifyCollectionTask(list, lock)).start();
}
}
} 结果:
抛出异常:java.util.ConcurrentModificationException
说明:
常用的集合类ArrayList、Map等在多线程操作同一对象时会发生不同步的线程而造成数据读取和写入错误;通常都是采用synchronized修饰符或Lock将那些方法括起来来确保它们在执行时不会被其他线程打扰。
这样做虽然解决了数据争用问题,但是在并发性方面付出了更多的代价,因为在迭代期间锁住整个List会阻塞其他线程,使它们在很长一段时间内不能访问这个列表。
2. 使用Vector集合
public class MultiThreadListTest {
public static void main(String[] args) {
Collection list = new Vector();
list.add(6);
list.add(3);
list.add(43);
list.add(88);
list.add(1);
Lock lock = new ReentrantLock();
//启动100个线程,在多线程环境下,测试集合的同步问题
for (int i = 0; i < 100; i++) {
new Thread(new ModifyCollectionTask(list, lock)).start();
}
}
} 结果:
抛出异常:java.util.ConcurrentModificationException
说明:
Vector虽然是线程同步的,但仅仅把ArrayList改成Vector还是不对。无论是ArrayList还是Vector,只要是实现Collection接口的,都要遵循fail-fast(快速失败)的检测机制,即在迭代是时候,不能修改集合的元素。一旦发现违法这个规定就会抛出异常。
事实上,Vector相对于ArrayList的线程同步,体现在对集合元素是否脏读上。即ArrayList允许脏读,而Vector特殊的机制,不会出现脏读,但是效率会很差。
3. 使用Collections工具类中的同步包装方法,将线程不安全ArrayList进行包装
public class MultiThreadListTest {
public static void main(String[] args) {
Collection list = Collections.synchronizedList(new ArrayList());
list.add(6);
list.add(3);
list.add(43);
list.add(88);
list.add(1);
Lock lock = new ReentrantLock();
//启动100个线程,在多线程环境下,测试集合的同步问题
for (int i = 0; i < 100; i++) {
new Thread(new ModifyCollectionTask(list, lock)).start();
}
}
} 结果:
抛出异常:java.util.ConcurrentModificationException
说明:
对于 Collections 的 synchronizedCollection 或者 synchronizedList 方法包装过的集合来说,对于 iterator() 方法是需要用户手工进行同步的。
4. 使用java.util.concurrent包中的ConcurrentLinkedQueue高效的同步集合 将ModifyCollectionTask类中的锁相关代码注释加上,恢复到最初。
public class MultiThreadListTest {
public static void main(String[] args) {
Collection list = new ConcurrentLinkedQueue();
list.add(6);
list.add(3);
list.add(43);
list.add(88);
list.add(1);
Lock lock = new ReentrantLock();
//启动100个线程,在多线程环境下,测试集合的同步问题
for (int i = 0; i < 100; i++) {
new Thread(new ModifyCollectionTask(list, lock)).start();
}
}
} 结果:
没出现异常。
说明:
对于 java.util.concurrent 中的任何集合都是经过精心设计的,无论迭代、增加、删除都是线程而全的,而且在迭代时不会抛了 ConcurrentModificationException 的异常。
三、参考资料 http://blog.csdn.net/itm_hadf/article/details/7506529
http://long-yu2.iteye.com/blog/1530278
http://blog.csdn.net/johnny901114/article/details/8696032