2012-03-13 96 views
3

的同步建议在遗留应用程序中,我有一个Vector来保存要按时间顺序排列的文件列表,并且多个线程要求它处理下一个文件。 (请注意,我认识到有可能使用更好的集合(可随意推荐),但我现在没有时间来改变这个大小。)需要关于Java Vector/ConcurrentModificationException的同步

在计划的时间间隔内,另一个线程检查工作目录,查看是否有文件因为出错而成为孤儿。如果系统异常繁忙,此线程调用的方法偶尔会引发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的同步混淆。作为一个额外的问题,声明方法是同步的,并用另一个同步块包装返回调用只是疯狂的编码?看起来多余。

编辑:这里是触摸列表的其他方法。

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(); 
    } 
} 
+0

那一行是抛出的异常? – Jivings 2012-03-13 18:40:20

+0

这很难说。纯粹基于你所展示的代码,改变getFileList来返回一个克隆是无济于事的,因为在你使用getFileList的唯一地方,它已经创建了一个克隆。总是返回一个克隆可能会让事情变得更好(但是根据你发布的代码,没有证据表明它是否会发生,也就是说你没有显示整个程序) – 2012-03-13 18:57:02

+0

@Jivings - 它被抛出循环'(文件wf :(矢量)m_dataStore.getFileList()。clone()){' – user1011251 2012-03-13 19:25:51

回答

0

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

+0

我刚刚编辑原始帖子,添加使用列表的其他方法。对不起,我是一个新手,无法得到第一个格式正确。 – user1011251 2012-03-13 19:18:14

5

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

1

“我通常通过使用Java中同步的,因为它涉及 到集合,简单地宣告方法,这种困惑不是 不够”

正确的。​​上的方法意味着一次只有一个线程可以进入该方法。但是,如果从多种方法中可以看到相同的集合,那么这并没有多大帮助。

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

注意获得收集以同步的方式,因为你在getFileList所做的一切,是远远不够的 - 它是访问需要同步。如果您只需要读取权限,克隆收集将(可能)解决问题。

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

+0

的确,我们一直在跑来跑去,就像白痴一样在全国各地同步。 – user1011251 2012-03-13 19:10:44

0

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

一旦你完成了这个任务,你的列表就会最终完成,并且只有在你修改它的元素时才能进行同步。只需要读取它的线程就不需要同步访问。他们可能最终会投票过时的名单,但我想这不是问题。这可以让你提高性能。

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

例如

包答案;

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 
} 

}