Синхронизация доступа к файлам на объекте 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<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

У меня есть два вопроса:

  • Как может произойти такая синхронизация? Обратите внимание, что я уверен, что существует только один экземпляр TableSaver.
  • Что это за штука в stacktrace: package.TableSaver.access$600(TableSaver.java:34)? Строка 34 - это строка с class TableSaver {. Может ли это быть причиной того, что синхронизация не работает?

Ответ 1

Google узнает, что это похоже на Windows. Здесь выдержка Ошибка 6354433:

Это проблема с платформой Windows с файлом с отображением памяти, т.е. MappedByteBuffer. В документе Java 5.0 doc для FileChannel указано, что "буфер и отображаемое им отображение останутся действительными до тех пор, пока сам буфер не будет собран с помощью мусора". Ошибка возникает, когда мы пытаемся повторно открыть filestore, а отображаемый байт-буфер не был GC. Поскольку для отображенного байтового буфера нет метода unmap() (см. Ошибку 4724038), мы находимся во власти базовой операционной системы, когда буфер освобождается. Вызов System.gc() может освободить буфер, но это не гарантия. Проблема не возникает в Solaris; может быть связано с тем, как разделенная память реализована в Solaris. Таким образом, работа для Windows не должна использовать файл с отображением памяти для таблиц транзакций.

Какую версию Java/Windows вы используете? Имеет ли он последние обновления?

Вот две другие связанные ошибки с некоторыми полезными сведениями:

  • Ошибка 4715154 - Файл с отображением памяти не может быть удален.
  • Ошибка 4469299 - Файлы с отображением памяти не являются GC'ed.

Что касается вашего второго вопроса, это просто автогенерированное имя класса для внутреннего или анонимного класса.

Ответ 2

Предполагая, что с кодом, который я видел это, возникают проблемы, когда в фоновом режиме работает сканер вирусов, который активно открывает файлы для сканирования их за кулисами. Если у вас есть резидентный антивирусный сканер, который проверяет файлы в фоновом режиме, попробуйте отключить его или, по крайней мере, отключите его для каталога, в котором вы читаете/записываете.

Ответ 3

Ваш код выглядит отлично. Вы уверены, что это не связано с разрешением файла? Имеет ли приложение права на запись в эту папку? К этому файлу?


[EDIT] Кажется, что это связано с Windows, а не с Java Запрошенная операция не может быть выполнена в файле с открытым пользователем разделом.

Ответ 4

Ваша синхронизация защищает только от доступа из вашего собственного процесса. Если вы хотите защитить от доступа из любого процесса, вы должны использовать блокировку файлов:

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

Ответ 5

У меня была эта проблема с некоторыми плотно пронизанными Java-кодом. Я взглянул на упомянутый диалог .NET, и пенни упала. Просто у меня есть соперничество за один и тот же файл среди разных потоков. Более внимательно рассмотрим утверждение (также) для некоторых внутренних дел. Поэтому лучше всего использовать synchronize-d вокруг общего объекта при обновлении.

Это работает, и ошибка растворяется в тумане.

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

      ...

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

см. также: