需要有关Java Vector / ConcurrentModificationException同步的建议

时间:2012-03-13 18:37:42

标签: java

在遗留应用程序中,我有一个Vector,它保存一个按时间顺序排列的文件列表,多个线程要求它处理下一个文件。 (请注意,我意识到可能有更好的收藏品使用(随意提示),但我现在没有时间改变这种程度。)

在预定的时间间隔内,另一个线程会检查工作目录,以查看是否有任何文件因为出错而显示为孤立状态。如果系统异常繁忙,此线程调用的方法偶尔会抛出ConcurrentModificationException。所以我知道至少有两个线程试图同时使用Vector。

这是代码。我相信问题是在返回的Vector上使用了clone()。

private synchronized boolean isFileInDataStore( File fileToCheck ){
    boolean inFile = false;
    for( File wf : (Vector<File>)m_dataStore.getFileList().clone() ){
        File zipName = new File( Tools.replaceFileExtension(fileToCheck.getAbsolutePath(), ZIP_EXTENSION) );
        if(wf.getAbsolutePath().equals(zipName.getAbsolutePath()) ){
            inFile = true;
            break;
        }
    }
  return inFile;
}

getFileList()方法如下:

public synchronized Vector<File> getFileList() {
    synchronized(fileList){
        return fileList;
    }
}

作为一个快速修复,将更改getFileList方法以返回向量的副本,如下所示?

public synchronized Vector<File> getFileListCopy() {
    synchronized(fileList){
        return (Vector<File>)fileList.clone();
    }
}

我必须承认,我通常会因为它与集合有关而在Java中使用synchronized而感到困惑,因为仅仅声明这样的方法是不够的。作为一个额外的问题,是将方法声明为同步并将返回调用与另一个同步块一起包装,只是疯狂编码?看起来多余。

编辑:以下是触及列表的其他方法。

public synchronized boolean addFile(File aFile) {
    boolean added = false;
    synchronized(fileList){
    if( !fileList.contains(aFile) ){
        added = fileList.add(aFile);
}
}
notifyAll();
return added;
}

public synchronized void removeFile( File dirToImport, File aFile ) {
    if(aFile!=null){
        synchronized(fileList){
            fileList.remove(aFile);
        }
        // Create a dummy list so I can synchronize it.
        List<File> zipFiles = new ArrayList<File>(); 
        synchronized(zipFiles){
            // Populate with actual list
            zipFiles = (List<File>)diodeTable.get(dirToImport);
            if(zipFiles!=null){
                zipFiles.remove(aFile);
                // Repopulate list if the number falls below the number of importer threads.
                if( zipFiles.size()<importerThreadCount ){
                    diodeTable.put(dirToImport, getFileList( dirToImport ));
                }
            }
        }
        notifyAll();
    }
}

4 个答案:

答案 0 :(得分:5)

基本上,这里有两个不同的问题:sycnhronization和ConcurrentModificationException。与{例}相比,Vector ArrayList在内部同步,因此add()get()等基本操作不需要同步。但是,如果您在ConcurrentModificationException上进行迭代并在此期间修改它,即使是单个线程也可以获得Vector,例如通过插入元素。因此,如果您在for循环内执行了修改操作,即使使用单个线程也可以中断Vector。现在,如果您将Vector退回到课堂之外,则不会阻止任何人在代码中没有正确同步的情况下修改它。原始版本fileListgetFileList()上的同步毫无意义。返回副本而不是原始副本可能有所帮助,因为可以使用允许在迭代时进行修改的集合,例如CopyOnWriteArrayList(但请注意修改的额外成本,在某些情况下可能是一个showstopper)。

答案 1 :(得分:1)

  

“我一般对使用Java中的synchronized感到困惑   属于集合,因为简单地声明方法不是这样   足够的“

正确。方法上的synchronized表示一次只有一个线程可以进入该方法。但是如果从多种方法中可以看到相同的集合,那么这无济于事。

为了防止两个线程同时访问同一个集合,他们需要在相同的对象上进行同步 - 例如集合本身。您已在某些方法中执行此操作,但isFileInDataStore似乎访问getFileList返回的集合而未进行同步。

请注意,正如您在getFileList中所做的那样,以同步方式获取集合是不够的 - 这是需要同步的访问。如果您只需要读访问权限,克隆该集合(可能)可以解决问题。

除了查看同步之外,我建议您追踪涉及哪些线程 - 例如打印出异常的调用堆栈和/或使用调试器。最好真正了解正在发生的事情,而不仅仅是同步和克隆,直到错误消失为止!

答案 2 :(得分:0)

m_dataStore在哪里更新?如果它不同步,这可能是罪魁祸首。

答案 3 :(得分:0)

首先,如果你没有,你应该把你的逻辑移到m_dataStore的任何一类。

完成此操作后,请将列表设为最终,并在仅修改其元素时对其进行同步。只需要读取它的线程,不需要同步访问。他们最终可能会对过时的列表进行轮询,但我认为这不是问题。这可以提高性能。

据我所知,你只需要在添加和删除时同步,只需要锁定你的列表。

e.g。

包裹答案;

import java.util.logging.Level; import java.util.logging.Logger;

公共类示例{

public static void main(String[] args)
{
    Example c = new Example();
    c.runit();
}


public void runit()
{
    Thread.currentThread().setName("Thread-1");

    new Thread("Thread-2")
    {
        @Override
        public void run() {               
            test1(true);
        }            
    }.start();

    // Force a scenario where Thread-1 allows Thread-2 to acquire the lock
    try {
        Thread.sleep(1000);
    } catch (InterruptedException ex) {
        Logger.getLogger(Example.class.getName()).log(Level.SEVERE, null, ex);
    }

    // At this point, Thread-2 has acquired the lock, but it has entered its wait() method, releasing the lock

    test1(false);
}

public synchronized void test1(boolean wait)    
{
    System.out.println( Thread.currentThread().getName() +  " : Starting...");
    try {
        if (wait)
        {
            // Apparently the current thread is supposed to wait for some other thread to do something...
            wait();
        } else {
            // The current thread is supposed to keep running with the lock
            doSomeWorkThatRequiresALockLikeRemoveOrAdd();
            System.out.println( Thread.currentThread().getName() +  " : Our work is done. About to wake up the other thread(s) in 2s...");
            Thread.sleep(2000);

            // Tell Thread-2 that it we have done our work and that they don't have to spare the CPU anymore.
            // This essentially tells it "hey don't wait anymore, start checking if you can get the lock"
            // Try commenting this line and you will see that Thread-2 never wakes up...
            notifyAll();

            // This should show you that Thread-1 will still have the lock at this point (even after calling notifyAll).
            //Thread-2 will not print "after wait/notify" for as long as Thread-1 is running this method. The lock is still owned by Thread-1.
            Thread.sleep(1000);
        }


        System.out.println( Thread.currentThread().getName() +  " : after wait/notify");
    } catch (InterruptedException ex) {
        Logger.getLogger(Example.class.getName()).log(Level.SEVERE, null, ex);
    }
}

private void doSomeWorkThatRequiresALockLikeRemoveOrAdd()
{
    // Do some work that requires a lock like remove or add
}

}