2 回答

TA贡献1993条经验 获得超6个赞
该解决方案基于ConcurrentHashMap#computeIfAbsent,有两个假设:
多个线程读取同一个文件不是问题。
虽然文档说由于阻塞,计算应该简单而简短,但我相信这只是相同密钥(或存储桶/条带)访问的问题,并且仅适用于更新(而不是读取)?在这种情况下,这不是问题,因为我们要么成功计算了 value,要么 throw
IllegalArgumentException
。
使用它,我们通过将其作为放置密钥所需的计算来实现每个密钥只打开一次文件。
public String getLine(int lineNr) throws IllegalArgumentException {
if (lineNr > nrLines) {
throw new IllegalArgumentException();
}
return cache.computeIfAbsent(lineNr, (l) -> {
try (Stream<String> st = Files.lines(path)) {
Optional<String> optionalLine = st.skip(lineNr - 1).findFirst();
if (optionalLine.isPresent()) {
return optionalLine.get();
} else {
nrLines = nrLines > lineNr ? lineNr : nrLines;
throw new IllegalArgumentException();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
});
}
我通过产生 3 个线程来“验证”第二个假设,其中:
Thread1 通过无限循环(永远阻塞)来计算密钥 0。
Thread2 尝试放入键 0,但从未这样做,因为 Thread1 阻塞。
Thread3 尝试放入键 1,并立即这样做。
试试看,也许它有效,或者假设是错误的,它很糟糕。Map 在内部使用存储桶,因此即使使用不同的键,计算也可能成为瓶颈,因为它锁定了存储桶/条带。

TA贡献1946条经验 获得超4个赞
混ConcurrentMap在一起synchronized(this)可能不是正确的方法。包中的类java.util.concurrent是为特定用例设计的,并尝试在内部优化同步。
相反,我建议先尝试一个设计良好的缓存库,看看性能是否足够好。一个例子是咖啡因。根据Population docs,它为您提供了一种声明如何加载数据的方法,即使是异步的:
AsyncLoadingCache<Key, Graph> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.MINUTES)
// Either: Build with a synchronous computation that is wrapped as asynchronous
.buildAsync(key -> createExpensiveGraph(key));
// Or: Build with a asynchronous computation that returns a future
.buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));
添加回答
举报