I have a method that runs every 5 minutes and removes files from a cache
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();
}
}
This method fails with a ConcurrentModificationException about 2-3 times a day. I am unable to see why since the method can only run once at the same time and I am not iterating at the same time as removing from the HashMap.
It fails at line .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]
6_000_000_000Lfor readability.ConcurrentHashMapand see if this yields better results. Any time you use a non-synchronized object with multiple threads, you will run into these kinds of problems.HashMapmay produce basically any weird behaviour possible. It will try to throw a ConcurrentModificationException, but that's your best case. Use aConcurrentHashMapor guard any possible modification with the appropriate tools (synchronizedblocks orLocks).