Случайный java.util.ConcurrentModificationException: null - PullRequest
0 голосов
/ 09 октября 2019

У меня есть метод, который запускается каждые 5 минут и удаляет файлы из кэша

private HashMap<String, CachedFile> cache = new HashMap<>();

@Scheduled(fixedDelay = 300000)
public void deleteFileCache() {

    //remove files last accessed > 12h
    cache.entrySet().stream().filter(entry -> LocalDateTime.now().minus(12, ChronoUnit.HOURS)
            .isAfter(entry.getValue().getLastAccessed())).forEach(entry -> {
        File file = new File(tempFolder, entry.getKey());
        boolean deleted = file.delete();
    });

    //remove entries from HashMap
    cache.entrySet().removeIf(entry -> LocalDateTime.now().minus(12, ChronoUnit.HOURS)
            .isAfter(entry.getValue().getLastAccessed()));

    //if little space left remove oldest files
    long freeSpace = tempFolder.getFreeSpace();
    while (freeSpace < 6000000000L) {
        Optional<String> fileToDelete = cache.entrySet().stream()
                .min(Comparator.comparing(stringCachedFileEntry -> stringCachedFileEntry.getValue().getLastAccessed()))
                .map(Map.Entry::getKey);

        fileToDelete.ifPresent(filename -> {
            new File(tempFolder, filename).delete();
            cache.remove(filename);
        });
        freeSpace = tempFolder.getFreeSpace();
    }
}

Этот метод завершается с ошибкой ConcurrentModificationException примерно 2-3 раза в день. Я не могу понять, почему, поскольку метод может выполняться только один раз в одно и то же время, и я не выполняю итерацию одновременно с удалением из HashMap.

Сбой в строке .min(Comparator.comparing(stringCachedFileEntry ->...

java.util.ConcurrentModificationException: null
        at java.util.HashMap$EntrySpliterator.forEachRemaining(HashMap.java:1704) ~[na:1.8.0_212]
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482) ~[na:1.8.0_212]
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472) ~[na:1.8.0_212]
        at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) ~[na:1.8.0_212]
        at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_212]
        at java.util.stream.ReferencePipeline.reduce(ReferencePipeline.java:479) ~[na:1.8.0_212]
        at java.util.stream.ReferencePipeline.min(ReferencePipeline.java:520) ~[na:1.8.0_212]
        at ch.my.app.server.FileCacheService.deleteFileCache(FileCacheService.java:113) ~[classes!/:1.0-SNAPSHOT]

1 Ответ

3 голосов
/ 09 октября 2019

A ConcurrentModificationException генерируется не только удалениями из повторного набора во время итерации. Вставки вызывают это также. Таким образом, вероятная причина для вас заключается в том, что ваш загрузочный контроллер Spring пишет на карту без синхронизации с помощью метода deleteFileCache.

Очевидное решение состоит в том, чтобы использовать некоторую потокобезопасную карту вместо HashMap, напримерConcurrentHashMap упомянуто в нескольких комментариях.

Например, документация Spliterator гласит:

... После связывания Spliterator должен, с максимальной отдачей. , выбросить ConcurrentModificationException, если обнаружено структурное вмешательство. …

Из вашей трассировки стека видно, что метод min использует Spliterator.

Структурные изменения включают как вставки, так и удаления (они, вероятно, не включают замену отображения вкарта, где изменяется только значение).

Ссылка: Документация Spliterator

...