weixin_42435096 2011-03-19 17:52
浏览 256
已采纳

学习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条回答 默认 最新

  • changchanghust 2011-03-20 00:49
    关注

    你是用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]

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(4条)

报告相同问题?

悬赏问题

  • ¥100 Jenkins自动化部署—悬赏100元
  • ¥15 关于#python#的问题:求帮写python代码
  • ¥20 MATLAB画图图形出现上下震荡的线条
  • ¥15 关于#windows#的问题:怎么用WIN 11系统的电脑 克隆WIN NT3.51-4.0系统的硬盘
  • ¥15 perl MISA分析p3_in脚本出错
  • ¥15 k8s部署jupyterlab,jupyterlab保存不了文件
  • ¥15 ubuntu虚拟机打包apk错误
  • ¥199 rust编程架构设计的方案 有偿
  • ¥15 回答4f系统的像差计算
  • ¥15 java如何提取出pdf里的文字?