我试图减少分段数据的锁定对象的内存使用量。查看我的问题here和here。或者假设您有一个字节数组,每16个字节可以(反)序列化为一个对象。我们称之为" row"行长度为16个字节。现在,如果您从编写器线程修改这样的行并从多个线程读取,则需要锁定。如果你的字节数组大小为1MB(1024 * 1024),这意味着65536行和相同数量的锁。
这有点太多了,我还需要更大的字节数组,我想把它减少到大致与线程数成比例的东西。我的想法是创建一个
ConcurrentHashMap<Integer, LockHelper> concurrentMap;
其中Integer
是行索引,在线程进入&#39;之前它将一个锁定对象放在这个地图中(从this answer得到这个想法)。但无论我怎么想,我都找不到一种真正的线程安全方法:
// somewhere else where we need to write or read the row
LockHelper lock1 = new LockHelper();
LockHelper lock = concurrentMap.putIfAbsent(rowIndex, lock1);
lock.addWaitingThread(); // is too late
synchronized(lock) {
try {
// read or write row at rowIndex e.g. writing like
bytes[rowIndex/16] = 1;
bytes[rowIndex/16 + 1] = 2;
// ...
} finally {
if(lock.noThreadsWaiting())
concurrentMap.remove(rowIndex);
}
}
你认为有可能使这个线程安全吗?
我觉得这看起来与concurrentMap.compute
contstruct非常相似(例如参见this answer),或者我是否可以使用此方法?
map.compute(rowIndex, (key, value) -> {
if(value == null)
value = new Object();
synchronized (value) {
// access row
return value;
}
});
map.remove(rowIndex);
是否值和&#39;同步&#39;我们已经知道计算操作是原子的吗?
// null is forbidden so use the key also as the value to avoid creating additional objects
ConcurrentHashMap<Integer, Integer> map = ...;
// now the row access looks really simple:
map.compute(rowIndex, (key, value) -> {
// access row
return key;
});
map.remove(rowIndex);
顺便说一下:从我们用Java编写这个计算机以来。从1.8开始?在JavaDocs中找不到这个
更新我发现了一个非常相似的问题here,其中包含userIds而不是rowIndices,请注意,该问题包含一个示例,其中包含一些问题,例如缺少final
,调用{{1在lock
- 子句内部并且没有缩小地图。此外,似乎a library JKeyLockManager用于此目的,但I don't think it is thread-safe。
更新2:解决方案似乎非常简单,因为Nicolas Filotto指出如何避免删除:
try-finally
所以内存强度要小得多,但使用map.compute(rowIndex, (key, value) -> {
// access row
return null;
});
的简单段锁定在my scenario中至少快50%。
答案 0 :(得分:1)
价值和
synchronized
是否必要 知道计算操作是原子的吗?
我确认在这种情况下不需要添加synchronized
块,因为compute
方法是原子地完成的,如ConcurrentHashMap#compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)
的Javadoc中所述,已添加{ {1}}自BiFunction
起,我引用:
尝试计算指定键及其当前的映射 映射值(如果没有当前映射,则为
Java 8
)。 整个 方法调用以原子方式执行。一些尝试更新 其他线程在此映射上的操作可能会被阻止 计算正在进行中,因此计算应该很短 简单,不得尝试更新此null
的任何其他映射。
使用Map
方法尝试实现的内容可能完全是原子的,如果您使compute
始终返回BiFunction
以原子方式移除键,以便所有内容都将以原子方式完成。
null
这样,您将完全依赖map.compute(
rowIndex,
(key, value) -> {
// access row here
return null;
}
);
的锁定机制来同步对行的访问。