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