ConcurrentMap按需加载java



我正在开发一个需要线程安全的按需缓存。我有大约30K以上项目的数据(在一个文件中(,我只想在多线程游戏需要时获得这些数据。然而,我不确定我的方法是否是ConcurrentMap的computeInfoAbsent应该如何使用,如果不是,还有什么替代方案可以让我从一个文件中懒散地加载内容,而不用担心线程问题?如果对象存在于我的地图中,我想避免锁定,我已经使用CHM在读取时进行了读取。

我已经预先缓存了要加载的文件名(即ID(,以确保它们存在,从而避免通过headers哈希映射进行持续检查。headers映射是只读的,在我的程序启动时只加载一次。

这就是我所做的:

private static final ConcurrentMap<Integer, ItemData> items = new ConcurentHashMap<>();
private static final HashMap<Integer, Byte> headers = new HashMap<>(); // pre loaded file names to avoid checking if file exists
public static ItemData getItem(int itemID) {
var item = items.get(itemID);
if (item != null) {
return item;
}
// if item doesn't exist in map, check if it exists in file on disk
if (!headers.containsKey(itemID)) {
return null;
}
// if item exists in file add it to cache
return items.computeIfAbsent(itemID, k -> {
try (var dis = new DataInputStream(new FileInputStream("item.bin"))) {
var data = new ItemData(itemID);
data.load(dis); // obtains only data for one item
return item;
} catch (IOException e) {
// ommited for brevity. logging goes here.
return null;
}
});
}

更新:预加载对我来说不是一个选项,我同意这样做可以解决线程问题,因为它只是只读的。但我的游戏资产加起来超过2GB。我不想在启动期间加载所有内容,因为文件中的某些项目可能永远不会被使用。因此,我正在寻找一种只在需要时加载它们的方法。

您编写了

如果对象存在于我的映射中,我想避免锁定,这是我在读取时使用CHM读取的。

我不知道你在哪里读到的,但肯定是错的。这甚至不是一个过时的声明,即使是第一个版本也规定:

检索操作(包括get(通常不会阻止…

您的方法的总体结构很好。在对键进行并发第一次访问的情况下,可能有多个线程通过了第一次检查,但只有一个线程会在computeIfAbsent中进行实际检索,并且所有线程都会使用结果。对已加载项目的后续访问可以受益于第一次普通get访问。

还有一些需要改进的地方。

return items.computeIfAbsent(itemID, k -> {
try (var dis = new DataInputStream(new FileInputStream("item.bin"))) {
var data = new ItemData(k);
data.load(dis); // obtains only data for one item
return item;
} catch (IOException e) {
// may still do logging here
throw new UncheckIOException(e);
}
});

首先,虽然这是一种很好的日志记录方法(为了简洁起见,您省略了它(,但返回null并强制调用代码处理null不是一个好主意。您已经有了headers.containsKey(…)检查,告诉我们资源应该在那里,所以应用程序可能无法处理缺席,所以我们讨论的是一种特殊情况。

此外,您可以使用传递给函数的k参数,而不是从周围的作用域访问itemID。限制访问范围不仅更干净,在这种情况下,它将lambda表达式变成了一个非捕获表达式,这意味着它不需要每次都创建一个新对象,否则需要创建新对象来保存捕获的值。


如果您真的为所有ItemData读取相同的item.bin文件,您可以考虑使用内存映射I/O来共享数据,而不是使用DataInputStream读取数据。内存映射文件的ByteBuffer表示提供了几乎相同的方法来获取复合项,它甚至支持DataInputStream不支持的小端序处理。

最新更新