weixin_42435096
2011-03-19 17:52 阅读 234
已采纳

学习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 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]

    点赞 评论 复制链接分享
  • redstarofsleep redstarofsleep 2011-03-19 19:06

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

    点赞 评论 复制链接分享
  • lxbccsu lxbccsu 2011-03-19 19:26

    这与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()产生的,所以后面的修改就没异常了。

    点赞 评论 复制链接分享
  • lxbccsu lxbccsu 2011-03-19 21:27

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

    点赞 评论 复制链接分享
  • changchanghust changchanghust 2011-03-20 01:03

    补充: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异常。

    点赞 评论 复制链接分享

相关推荐