在Java对象上同步文件访问

我有一个对象负责将JTable状态持久化到磁盘。 它保存/加载可见列,它们的大小,位置等。下面是类定义中的一些有趣的部分。

 class TableSaver { Timer timer = new Timer(true); TableSaver() { timer.schedule(new TableSaverTimerTask(), 15000, SAVE_STATE_PERIOD); } synchronized TableColumns load(PersistentTable table) { String xml = loadFile(table.getTableKey()); // parse XML, return } synchronized void save(String key, TableColumns value) { try { // Some preparations writeFile(app.getTableConfigFileName(key), xml); } catch (Exception e) { // ... handle } } private class TableSaverTimerTask extends TimerTask { @Override public void run() { synchronized (TableSaver.this) { Iterator iterator = queue.iterator(); while (iterator.hasNext()) { PersistentTable table = iterator.next(); if (table.getTableKey() != null) { save(table.getTableKey(), dumpState(table)); } iterator.remove(); } } } } } 
  • 只存在一个TableSaver实例。
  • load()可以从许multithreading调用。 计时器显然是另一个线程。
  • loadFile()writeFile()不会留下打开的文件流 – 它们使用一个强大的,经过良好测试和广泛使用的库,它始终使用try ... finally来关闭流。

有时这会失败,例外情况如下:

 java.lang.RuntimeException: java.io.FileNotFoundException: C:\path\to\table-MyTable.xml (The requested operation cannot be performed on a file with a user-mapped section open) at package.FileUtil.writeFile(FileUtil.java:33) at package.TableSaver.save(TableSaver.java:175) at package.TableSaver.access$600(TableSaver.java:34) at package.TableSaver$TableSaverTimerTask.run(TableSaver.java:246) at java.util.TimerThread.mainLoop(Unknown Source) at java.util.TimerThread.run(Unknown Source) Caused by: java.io.FileNotFoundException: C:\path\to\table-MyTable.xml (The requested operation cannot be performed on a file with a user-mapped section open) at java.io.FileOutputStream.open(Native Method) at java.io.FileOutputStream.(Unknown Source) at java.io.FileOutputStream.(Unknown Source) at package.FileUtilWorker.writeFile(FileUtilWorker.java:57) ... 6 more 

所以我有两个问题:

  1. 这种同步怎么会失败? 请注意,我确信只有一个TableSaver实例。
  2. stacktrace中的这个东西是什么: package.TableSaver.access$600(TableSaver.java:34) ? 第34行是class TableSaver {的行。 这可能是同步不起作用的原因吗?

谷歌告诉我这似乎是Windows特定的。 这是Bug 6354433的摘录 :

这是内存映射文件的Windows平台问题,即MappedByteBufferFileChannel的Java 5.0 doc声明“缓冲区及其表示的映射将保持有效,直到缓冲区本身被垃圾收集”。 当我们尝试重新打开文件存储并且映射的字节缓冲区不是GC时,会发生错误。 由于映射字节缓冲区没有unmap()方法(参见bug 4724038),因此当缓冲区释放时,我们将受到底层操作系统的支配。 调用System.gc()可能会释放缓冲区但不保证。 Solaris上不会出现此问题; 可能是由于Solaris上实现共享内存的方式。 因此,Windows的解决方法是不要将内存映射文件用于事务信息表。

您使用的是什么Java / Windows版本? 它有最新的更新吗?

以下是另外两个相关的错误,其中包含一些有用的见解:

  • 错误4715154 – 无法删除内存映射文件。
  • 错误4469299 – 内存映射文件不是GC。

关于你的第二个问题,那只是内部或匿名类的自动生成的类名。

假设我看到的代码没有问题,当病毒扫描程序在后台运行时会发生这种情况,这会快速打开文件以在后台扫描它们。 如果您有一个内存驻留病毒扫描程序,它在后台检查文件,请尝试禁用它,或者至少禁用它来读取/写入目录。

你的代码看起来很好。 你确定它与文件许可无关吗? 应用程序是否具有此文件夹的写权限? 到这个文件?


[编辑]这似乎与Windows相关,而不是Java 无法在打开用户映射部分的文件上执行所请求的操作。

您的同步仅防止来自您自己的进程的访问。 如果要防止来自任何进程的访问,则必须使用文件锁定:

http://download.oracle.com/javase/1.4.2/docs/api/java/nio/channels/FileLock.html

我遇到了一些带有严格线程的Java代码的问题。 我看了一下引用的.NET对话,便士掉了下来。 简单地说,我在不同的线程中争用同一个文件。 对于一些内部人员来说,更密切地看待争论也是如此。 所以我最好的方法是在更新共享对象时同步-d

这有效,错误在雾中消失。

  private static ShortLog tasksLog = new ShortLog( "filename" ); private static Boolean tasksLogLock = false; ... synchronized( tasksLogLock ){ tasksLog.saveLastDatum( this.toString() ); } 

另见

  • 非最终字段的同步