代码之家  ›  专栏  ›  技术社区  ›  Konrad Garus

Java对象上的文件访问同步

  •  10
  • Konrad Garus  · 技术社区  · 14 年前

    我有一个对象负责坚持 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. 这种同步怎么会失败呢?请注意,我确信只有一个实例 表格表 .
    2. stacktrace中的内容是什么: package.TableSaver.access$600(TableSaver.java:34) ?第34行是 class TableSaver { . 这是同步不工作的原因吗?
    5 回复  |  直到 10 年前
        1
  •  9
  •   BalusC    14 年前

    Google 了解到这似乎是特定于Windows的。这是一份摘录 Bug 6354433 :

    这是Windows平台的内存映射文件问题,即 MappedByteBuffer . Java 5文档 FileChannel 声明“缓冲区及其表示的映射将保持有效,直到缓冲区本身被垃圾收集为止”。当我们试图重新打开文件存储时出错,并且映射的字节缓冲区不是gc。因为没有 unmap() 方法对于映射的字节缓冲区(参见bug 4724038),当缓冲区释放时,底层操作系统就可以控制它了。打电话 System.gc() 可能会释放缓冲区,但不能保证。该问题不会在Solaris上发生;可能是由于在Solaris上实现共享内存的方式造成的。因此,Windows的解决方案不是为事务信息表使用内存映射文件。

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

    下面是另外两个具有一些有用见解的相关Bug:


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

        2
  •  2
  •   Mike Q    14 年前

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

        3
  •  1
  •   Community Rick James    7 年前

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


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

        4
  •  0
  •   gpeche    14 年前

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

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

        5
  •  0
  •   Community Rick James    7 年前

    我在这个问题上使用了一些线程密集的Java代码。我看了一眼被引用的.NET对话,硬币掉了下来。很简单,我在不同的线程之间对同一个文件有争用。仔细观察,争论是( )对于某些内部结构也是如此。所以我最好的办法是 同步D 在更新共享对象时围绕它。

    这样做,错误就消失在雾中。

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

    也见 :