【发布时间】:2017-02-02 03:42:03
【问题描述】:
我正在尝试减少分段数据的锁定对象的内存使用量。请参阅我的问题here 和here。或者只是假设您有一个字节数组,并且每 16 个字节可以(反)序列化为一个对象。让我们称其为行长为 16 字节的“行”。现在,如果您从写入线程修改这样的行并从多个线程读取,则需要锁定。如果您的字节数组大小为 1MB (1024*1024),这意味着 65536 行和相同数量的锁。
这有点太多了,而且我需要更大的字节数组,我想将它减少到与线程数大致成比例的东西。我的想法是创建一个
ConcurrentHashMap<Integer, LockHelper> concurrentMap;
其中Integer 是行索引,在线程“进入”一行之前,它会在此映射中放置一个锁定对象(从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 构造(例如,参见 this answer),或者我什至可以使用这种方法吗?
map.compute(rowIndex, (key, value) -> {
if(value == null)
value = new Object();
synchronized (value) {
// access row
return value;
}
});
map.remove(rowIndex);
我们已经知道计算操作是原子的,因此值和“同步”是否必要?
// 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,在 @987654340 中调用 lock @-clause 并且没有缩小地图。似乎也有 a library JKeyLockManager 用于此目的,但 I don't think it is thread-safe。
更新 2:解决方案似乎非常简单,因为 Nicolas Filotto 指出了如何避免删除:
map.compute(rowIndex, (key, value) -> {
// access row
return null;
});
所以这确实是内存密集度较低,但synchronized 的简单段锁定在my scenario 中至少快 50%。
【问题讨论】:
-
也许您可以使用类似于 n-way-associative 缓存的排列方式?双向关联意味着一个锁用于偶地址行,一个锁用于奇地址行。根据线程的数量,您可以尝试使用锁的数量。如果访问中存在模式,则使用质数锁甚至原始哈希将行地址映射到匹配的锁对象可能会有所帮助。
-
条带化假定分布良好,但由于执行少量工作而遭受争用和锁抖动。而是考虑一种消息传递方法,以拥有一个编写器(或每个核心一个段)。然后,您可以使用破坏者或扁平组合方法,通过执行一批工作而不是遭受争用来惩罚调用。
-
@GeorgBisseling 那是我们已经测试过的,请参阅源的更新。
-
@BenManes 你有一些链接可以更详细地解释你的方法吗?就我而言,我可以假设我只有一位作家,所以这会很有趣。
-
@Karussell 如果您不需要请求线程等待响应,这些技术效果最好。请参阅disruptor、flat combining 和 caffeine,了解记录 => 批处理 => 锁定 => 重放的不同变化。
标签: java multithreading concurrency locking