如何使这个类线程安全?

时间:2013-04-22 18:17:59

标签: java thread-safety

Vector是线程安全的,但在此实现中它似乎不起作用。使方法同步也无济于事,java.util.ConcurrentModificationException被抛出。

可以采取哪些措施来解决这个问题呢?

我应该去java.util.concurrent.CopyOnWriteArrayList吗?

import java.util.Iterator;
import java.util.Vector;

public class VectorExample {

    private Vector<Object> v;

    public  VectorExample() {    
        v = new Vector<Object>();
    }

    public  void  addToVector(Object o) {
        v.add(o);
    }

    public Vector<Object> getV() {
        return v;
    }

    public static void main(String[] args) {    
        final VectorExample v = new VectorExample();
        v.addToVector("obj1");
        v.addToVector("obj2");
        v.addToVector("obj3");
        v.addToVector("obj4");
        v.addToVector("obj5");
        v.addToVector("obj6");    

        new Thread(new Runnable() {
            @Override
            public  void run() {
                Iterator<Object> it = v.getV().iterator();

                while(it.hasNext()) {
                    System.out.println(it.next().toString());
                }    
            }
        }).start();

        new Thread(new Runnable() {    
            @Override
            public  void run() {
                v.addToVector("Obj11");
                v.addToVector("Obj12");
                v.addToVector("Obj13");
                v.addToVector("Obj14");
                v.addToVector("Obj15");
                v.addToVector("Obj16");
            }    
        }).start();    
    }
}

5 个答案:

答案 0 :(得分:2)

看起来你正在获得异常,因为你正在同时迭代和修改集合。您可以尝试克隆集合并迭代克隆。

答案 1 :(得分:2)

仅通过向现有方法添加同步就无法解决问题,因为它涉及交叉使用迭代器和变异。

Javadoc

  

一个线程通常不允许修改Collection,而另一个线程正在迭代它。通常,在这些情况下,迭代的结果是不确定的。如果检测到此行为,某些Iterator实现(包括JRE提供的所有通用集合实现的实现)可能会选择抛出此异常。执行此操作的迭代器称为失败快速迭代器,因为它们快速而干净地失败,而不是在未来的未确定时间冒着任意的,非确定性行为的风险。

 synchronized (v) { ... }

围绕第一个run()方法的主体并使addToVector同步将通过确保迭代器的整个生命周期与向量的突变相互排斥来解决当前问题。

要解决更大的问题,客户端需要知道在迭代器的整个生命周期内进行同步,您应该公开同步的fold方法,而不是暴露迭代器。这样,每个读者都不需要同步他们的整个阅读。

interface Folder<IN, OUT> {
  OUT foldOne(OUT x, IN element);
}

<OUT>
synchronized OUT foldLeft(Folder<? super Object, OUT> folder, OUT x) {
  for (Object element : v) {
    x = folder.foldOne(x, element);
  }
  return x;
}

或者,如果迭代是一个不常见的操作,只需将迭代器返回到向量的副本中。

答案 2 :(得分:2)

一种可能性:在每个运行方法中放置同步块:

    synchronized ( v ) {
        Iterator<Object> it = v.getV().iterator();

        while(it.hasNext())
        {
            System.out.println(it.next().toString());
        }
    }

    synchronized ( v ) {
        v.addToVector("Obj11");
        v.addToVector("Obj12");
        v.addToVector("Obj13");
        v.addToVector("Obj14");
        v.addToVector("Obj15");
        v.addToVector("Obj16");
    }

另一个提示:

第一个run方法的代码可以在VectorExample中,比如dump()或者其他什么。 这将使同步更容易。

答案 3 :(得分:0)

问题在于您需要保护整个迭代,而不仅仅是get()

iterator类中的Vector在写入Vector时变为无效。如果之后使用它将抛出ConcurrentModificationException

有两种方法可以解决这个问题。您可以在同一个锁对象上同步包含循环和添加调用的代码块,或者您可以使用不同的集合,允许在输入新数据后对旧数据进行迭代。

答案 4 :(得分:0)

迭代时不能修改集合。您可以制作集合的副本并迭代副本或同步集合上的两个块。

synchronized(v) {
  ..
}