学习Java多线程和集合,关于集合的同步问题,请教版上高手!
学习Java其实好久了,需要的时候也能写出个可以跑得程序,但是对其中很多机制没有仔细研究过。最近觉得该深入学习一下,于是抱着Java Core I 开始看了。
前段时间看了Java集合,目前学的是多线程。发现多线程里确实有很多东西值得好好研究。首先同步就是一个不小的问题。今天主要对集合的同步问题进行了一下小小的测试,出现了几个我自己没弄明白的问题,希望版上高手给予解答。

多线程的同步主要是对多个线程共享一个资源时的访问控制,避免出现“乱套”现象。Java提供多种机制进行同步控制,比如锁和条件对象,synchronized关键字等。而集合经常作为这种资源被多个线程共享,而且集合框架中本身也对多线程同步进行过考虑,比如有Vector,HashTable等类。于是自己写了个简单的程序,进行下测试,到底哪种方法能有效控制集合在多线程并发中的同步。
设置非常简单的一个场景,有一个Student集合,对该集合采用迭代器进行遍历之后,又对其添加了一个元素。由于添加元素是改变集合结构的操作,所以集合如果在迭代器构造之后发生改变,就会抛出ConcurrentModificationException异常。
Student类代码,很简单:

[code=Java]
class Student{
public Student(String name, int age){
this.name = name;
this.age = age;

}
String name;
int age;

public String  toString(){
    return "I am "+name+" , "+age+" years old.";
}

}
[/code]
1. 创建一个修改Collection的线程,实现Runnable接口。不采取任何同步措施。
[code=Java]
class ModifyCollectionTask implements Runnable{
public ModifyCollectionTask(Collection slist){
this.slist = slist;
}
public void run(){

// 遍历学生列表,
for(Student s : slist){
System.out.println(Thread.currentThread().getName());
System.out.println(s);
}
// 向学生列表添加元素
slist.add(new Student("Katie", 30));

}
Collection slist;
}
[/code]

在Main函数里启动100个线程,
[code=Java]
public class SyncCollection {
public static void main(String[] args) {

    Collection<Student> slist = new ArrayList<Student>();
    slist.add(new Student("AAA",10));
    slist.add(new Student("BBB",12));
    slist.add(new Student("CCC",14));
    slist.add(new Student("DDD",16));
    slist.add(new Student("EEE",18));

    for(int i=0;i<100;i++){
        new Thread(new ModifyCollectionTask(slist)).start();
    }

}
[/code]
很明显,没有同步控制,那么很快就抛出了ConcurrentModificationException异常

2. 由于Vector类是线程安全的动态数组,所以,将集合实现改为Vector,在线程run方法中没做任何修改

[code=Java]
// 使用Vector
List sVector = new Vector();
sVector.add(new Student("AAA",10));
sVector.add(new Student("BBB",12));
sVector.add(new Student("CCC",14));
sVector.add(new Student("DDD",16));
sVector.add(new Student("EEE",18));

    for(int i=0;i<100;i++){
        new Thread(new ModifyCollectionTask(sVector)).start();
    }

[/code]
结果还是发生了ConcurrentModificationException异常。这让我有点怀疑Vector的同步机制。

3. 使用Collections工具类中的同步包装方法,将线程不安全ArrayList进行包装,而线程实现方法没有改动

[code=Java]
// 使用Collections工具类中的同步包装器
List slist2 = Collections.synchronizedList(new ArrayList());
slist2.add(new Student("AAA",10));
slist2.add(new Student("BBB",12));
slist2.add(new Student("CCC",14));
slist2.add(new Student("DDD",16));
slist2.add(new Student("EEE",18));

    for(int i=0;i<100;i++){
        new Thread(new ModifyCollectionTask(slist2)).start();
    }

[/code]
结果还是发生了异常,不明白。。。

4. 下面使用synchronized关键字进行同步控制,对线程实现代码进行了修改

[code=Java]
class syncModifyListTask implements Runnable{
public syncModifyListTask(Collection slist){
this.slist = slist;
}
public void run(){

synchronized(slist){
// 遍历学生列表
for(Student s : slist){
System.out.println(Thread.currentThread().getName());
System.out.println(s);
}
// 向学生列表添加元素
slist.add(new Student("Katie", 30));
}
}
Collection slist;
}
[/code]
由于有了synchronized关键字对代码片段进行了保护,所以没有出现异常

5. 使用java.util.concurrent包中的高效的同步集合, ConcurrentLinkedQueue,线程实现代码还是用ModifyCollectionTask

[code=Java]
Collection concurrentCollection = new ConcurrentLinkedQueue();
concurrentCollection.add(new Student("AAA",10));
concurrentCollection.add(new Student("BBB",12));
concurrentCollection.add(new Student("CCC",14));
concurrentCollection.add(new Student("DDD",16));
concurrentCollection.add(new Student("EEE",18));

    for(int i=0;i<100;i++){
        new Thread(new ModifyCollectionTask(concurrentCollection)).start();
    }

[/code]
结果也没有出现异常,证明高效的同步集合还是很给力的!

将上述的各种集合同步方法进行一遍测试之后,发现Vector和Collections中的同步包装方法都不能保证同步,我就很纳闷儿,是我的使用方法出现了问题,还是这两种集合同步本身就做的不好?

希望版上的高手给予解答,谢谢!!!

5个回答

你是用for(Student s : slist)来遍历列表的,即相当于用slist.iterator()来进行遍历。
出于数据同步和性能的考虑,ArrayList和Vector的iterator都是快速失败的。也就是说,在遍历过程中,如果发现列表的内容在其他地方被修改了,iterator.next方法就会抛出ConcurrentModificationException异常,终止遍历。这也就是1,2的情况会挂掉的原因。

而Vector的线程安全是指它的get,add等方法,是通过synchronized修饰的,可以线程安全的修改和读取单个数据。

[quote]The Iterators returned by Vector's iterator and listIterator methods are fail-fast: if the Vector is structurally modified at any time after the Iterator is created, in any way except through the Iterator's own remove or add methods, the Iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the Iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future. The Enumerations returned by Vector's elements method are not fail-fast.[/quote]
参考资料:[url]http://download.oracle.com/javase/6/docs/api/java/util/Vector.html[/url]

3这种情况,Collections.synchronizedList(new ArrayList())返回的包装类,跟Vector情况类似,get,add等方法都是线程安全的,但iterator,返回的其实是被包装对象的iterator,因为被包装的ArrayList的iterator是快速失败的,所以这种情况也是会抛ConcurrentModificationException异常的。

4就不用多解释了,整个for循环遍历是在synchronized块里执行的,当然同步了。

5这种情况,ConcurrentLinkedQueue的iterator不会抛ConcurrentModificationException,并保证能遍历构造这个iterator时,队列里的元素,但不保证能反映后续的修改。
参考资料:[url]http://download.oracle.com/javase/6/docs/api/java/util/concurrent/ConcurrentLinkedQueue.html#iterator()[/url]

Vector和HashTable别用了,这2个是老的,早就不建议用了,用List和HashMap替代.

这与Vector的同步机制一点关系都没有。

在你的run方法中
[code="java"]
for(Student s : slist){
System.out.println(Thread.currentThread().getName());
System.out.println(s);
}
// 向学生列表添加元素
slist.add(new Student("Katie", 30));
}
[/code]

上面的for循环只有那些实现了 Iterable接口的类型才会正确运行;
Vector 的 iterator 方法返回了一个迭代器,而这个迭代器却是非线性安全的:如果在迭代器创建后的任意时间从结构上修改了Vector,则迭代器将抛出ConcurrentModificationException;

那个add是安全的。这个才是Vector中的同步方法。

其实并发问题就是run()产生的,所以后面的修改就没异常了。

看看API就很明白了。

public static List synchronizedList(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());
}
不遵从此建议将导致无法确定的行为。

补充:3情况,把for的遍历改成
[code="java"]
for(int i=0; i System.out.println(Thread.currentThread().getName());
System.out.println(((List)slist).get(i));
}[/code]

或者构造
[code="java"]Collection slist2 = Collections.synchronizedCollection(new ConcurrentLinkedQueue()); [/code]

都可以避免遍历的时候抛出ConcurrentModificationException异常。

Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问