2010-08-04 17 views
2

我有多个线程正在将我的'数据'对象序列化为文件。文件名是基于2场从对象锁定虚拟机中单个线程的文件

 
    class Data { 
    org.joda.DateTime time; 
    String title; 

    public String getFilename() { 
     return time.toString() + '_' + title + ".xml"; 
    } 

这可能是2个数据对象将具有相同的“时间”和“标题”,所以相同的文件名。

这是可以接受的,我很高兴能够被保存。 (如果这些数据对象相同,它们可能是相同的数据对象)

我的问题是两个(或更多)线程在同一时间写入文件,从而导致格式错误的XML。

我看了一下java.nio.channels.FileLock,但它是针对VM范围锁定的,并且不适用于线程内部锁定。

我可以在DataIO.class上同步(但这会导致巨大的开销,因为我真的只想在单个文件上同步)。

因为多个File对象可以表示同一个System-File,所以File对象上的同步将无用。

代码如下:

 
class DataIO { 
    public void writeArticleToFile(Article article, String filename, boolean overwrite) throws IOException { 
    File file = new File(filename); 
    writeArticleToFile(article, file, overwrite); 
    } 

    public void writeDataToFile(Data data, File file, boolean overwrite) throws IOException { 
    if (file.exists()) { 
     if (overwrite) { 
     if (!file.delete()) { 
      throw new IOException("Failed to delete the file, for overwriting: " + file); 
     } 
     } else { 
     throw new IOException("File " + file + " already exists, and overwrite flag is set to false."); 
     } 
    } 

    File parentFile = file.getParentFile(); 
    if (parentFile != null) { 
     file.getParentFile().mkdirs(); 
    } 

    file.createNewFile(); 

    if (!file.canWrite()) { 
     throw new IOException("You do not have permission to write to the file: " + file); 
    } 

    FileOutputStream fos = new FileOutputStream(file, false); 
    try { 
     writeDataToStream(data, fos); 
     logger.debug("Successfully wrote Article to file: " + file.getAbsolutePath()); 
    } finally { 
     fos.close(); 
    } 
    } 
} 

回答

1

你可以实习生(),这是文件名的字符串。然后在interned字符串上同步。

class DataIO { 
    public void writeArticleToFile(Article article, String filename, boolean overwrite) throws IOException { 
    synchronized(filename.intern()) { 
     File file = new File(filename); 
     writeArticleToFile(article, file, overwrite); 
    } 
    } 
+0

绝对是最简单的解决方案。不是没有风险,但是因为锁定对象的最佳做法是不可公开访问的。 (和一个interned字符串总是可用的任何地方) – 2010-08-04 15:19:38

+0

这是真实的,很好的做法。但是,您并未锁定字符串本身,更多的是使用该字符串形成DataIO类中锁的基础。查看修改后的帖子 – 2010-08-04 15:30:34

+0

这真的是一个非常整洁的解决方案,但正如柯克沃尔所说,我可能需要在其他地方的字符串(特别是阅读文件等)。但是,如果我在文件名前面添加了一些不寻常的,令人费解的字符串(可能是完全限定的类名),然后锁定THAT字符串的intern(),那么需要精确字符串对象几乎为0的机会几乎是可以接受的对我来说。 – barryred 2010-08-04 15:31:42

2

如果我正确读取这个数据,你有一个数据对象代表一个文件。

您可以考虑根据Data对象创建一个条带集。可能有

ConcurrentMap<Data,Lock> lockMap = new ConcurrentHashMap<Data,Lock>(); 

没有的ConcurrentHashMap的,当你想写信给这个对象,你可以这样做:

Lock lock = lockMap.get(someMyDataObject); 
lock.lock(); 
try{ 
    //write object here 
}finally{ 
    lock.unlock(); 
} 

请记住,您将不得不写的hashCode和equals基于标题方法日期时间

0

我同意使用同步是您应该使用的技术。您需要的是每个文件置换的独特对象,更重要的是每次都有相同的对象。一个可能的办法是创建一个类调用的FileLock:

public class FileLock { 
    DateTime time; 
    String title; 

    public FileLock(DateTime time, String title) { 
     this.time = time; 
     this.title = title; 
    } 

    override equals/hashCode based on those two properties 

    static Hashtable<FileLock, FileLock> unqiueLocks = new Hashtable<FileLock, FileLock>(); 
    static lockObject = new Object(); 

    public static FileLock getLock(DateTime time, String title) { 
     synchronized (lockObject) { 
      FileLock lock = new FileLock(time, title); 
      if (unqiueLocks.ContainsKey(lock)) { 
       return unqiueLocks.get(lock); 
      } 
      else { 
       unqiueLocks.put(lock, lock); 
       return lock; 
      } 
     } 
    } 
} 

然后调用者会使用它像:

synchronized (FileLock.getLock(time, title)) { 
    ... 
} 

请记住这有内存泄漏,因为Hashtable中保持与新的文件/时间增长排列。如果需要,可以修改此技巧,以便getLock的调用方还调用您用来保持Hashtable清洁的releaseLock方法。

相关问题