【问题标题】:File access synchronized on Java object在 Java 对象上同步文件访问
【发布时间】:2011-04-05 21:23:21
【问题描述】:

我有一个对象负责将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<PersistentTable> iterator = queue.iterator();
                while (iterator.hasNext()) {
                    PersistentTable table = iterator.next();
                    if (table.getTableKey() != null) {
                        save(table.getTableKey(), dumpState(table));
                    }
                    iterator.remove();
                }
            }
        }
    }
}
  • TableSaver 永远只存在一个实例。
  • load() 可以从多个线程中调用。 Timer 显然是另一个线程。
  • 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.<init>(Unknown Source)
    at java.io.FileOutputStream.<init>(Unknown Source)
    at package.FileUtilWorker.writeFile(FileUtilWorker.java:57)
    ... 6 more

所以我有两个问题:

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

【问题讨论】:

  • 您可以发布您的writeFile() 代码吗?
  • 关于#2,不,这不是问题。这只是从嵌套的内部类调用封闭类的私有成员的编译器人工制品。

标签: java multithreading


【解决方案1】:

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

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

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

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


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

【讨论】:

  • 如果我使用java.io.FileOutputStreamjava.io.FileInputStream而不是NIO怎么办?
  • 它在后台使用 NIO。
  • 那么我们应该使用什么呢?你能告诉我我在台式机 Windows 7 上遇到同样的问题,而在笔记本电脑上很好,在 linux 上没有问题。我用过 printwriter、bufferedwriter 和 nio,都有同样的问题
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    您的代码看起来不错。您确定它与文件权限无关吗?应用程序是否对该文件夹有写入权限?到这个文件?


    [编辑] 这似乎与 Windows 相关,而不是 Java The requested operation cannot be performed on a file with a user-mapped section open.

    【讨论】:

      【解决方案4】:

      我在一些线程紧密的 Java 代码中遇到了这个问题。我看了一下引用的 .NET 对话,结果一分钱都掉了。只是我在不同的线程之间争用同一个文件。更仔细地观察,对于某些内部结构的争论也是(也是)。所以我最好的做法是在更新共享对象时synchronize-d

      这行得通,错误消失在迷雾中。

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

      另见

      【讨论】:

        【解决方案5】:

        您的同步只能防止您自己的进程进行访问。如果你想防止来自任何进程的访问,你必须使用文件锁定:

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

        【讨论】:

          猜你喜欢
          • 2016-11-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-12-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多