Java中的并发字节数组访问,尽可能少的锁

时间:2016-09-24 09:53:28

标签: java multithreading concurrency locking

我试图减少分段数据的锁定对象的内存使用量。查看我的问题herehere。或者假设您有一个字节数组,每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%。

1 个答案:

答案 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; } ); 的锁定机制来同步对行的访问。