在Java对象上同步文件访问

时间:2010-08-30 17:35:24

标签: java multithreading

我有一个对象负责将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()。计时器显然是另一个主题。
  • 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. 的一个实例
  3. stacktrace中的这个东西是什么:package.TableSaver.access$600(TableSaver.java:34)?第34行是class TableSaver {的行。这可能是同步不起作用的原因吗?

5 个答案:

答案 0 :(得分:9)

Google了解到这似乎是Windows特有的。以下是Bug 6354433的摘录:

  

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

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

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


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

答案 1 :(得分:2)

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

答案 2 :(得分:1)

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


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

答案 3 :(得分:0)

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

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

答案 4 :(得分:0)

我遇到了一些紧密线程的Java代码。我看了一下引用的.NET对话,下了便士。简单地说,我在不同的线程中争用同一个文件。对于一些内部人员而言,更密切关注的是()。因此,我最好的做法是在更新共享对象时 synchronize-d

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

    private static   ShortLog   tasksLog     = new ShortLog( "filename" );
    private static   Boolean    tasksLogLock = false;

      ...

    synchronized( tasksLogLock ){
        tasksLog.saveLastDatum( this.toString() );
    }

另见