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

我正在尝试减少分段数据的锁定对象的内存使用量。 在这里和这里看我的问题。 或者假设你有一个字节数组,每16个字节可以(de)序列化为一个对象。 我们将其称为行,行长度为16个字节。 现在,如果您从编写器线程修改这样的行并从多个线程读取,则需要锁定。 如果你的字节数组大小为1MB(1024 * 1024),这意味着65536行和相同数量的锁。

这有点太多了,我还需要更大的字节数组,我想把它减少到大致与线程数成比例的东西。 我的想法是创造一个

ConcurrentHashMap concurrentMap;

其中Integer是行索引,在线程“输入”行之前,它会在此映射中放置一个锁定对象(从这个答案中得到了这个想法)。 但无论我怎么想,我都找不到真正的线程安全的方法:

 // 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 eg writing like bytes[rowIndex/16] = 1; bytes[rowIndex/16 + 1] = 2; // ... } finally { if(lock.noThreadsWaiting()) concurrentMap.remove(rowIndex); } } 

你认为有可能使这个线程安全吗?

我觉得这看起来非常类似于concurrentMap.compute contstruct(例如看到这个答案 ),或者我甚至可以使用这种方法?

 map.compute(rowIndex, (key, value) -> { if(value == null) value = new Object(); synchronized (value) { // access row return value; } }); map.remove(rowIndex); 

是否需要值和’synchronized’,因为我们已经知道计算操作是primefaces的?

 // null is forbidden so use the key also as the value to avoid creating additional objects ConcurrentHashMap map = ...; // now the row access looks really simple: map.compute(rowIndex, (key, value) -> { // access row return key; }); map.remove(rowIndex); 

顺便说一句:从我们用Java开始这个计算时。 从1.8开始? 在JavaDocs中找不到这个

更新:我在这里找到一个非常类似的问题,其中userIds而不是rowIndices,请注意该问题包含一个示例,其中包含缺少final几个问题,在try-finally -clause中调用lock以及缺少缩小映射。 此外,似乎有一个库JKeyLockManager用于此目的,但我不认为它是线程安全的 。

更新2:解决方案似乎非常简单,因为Nicolas Filotto指出如何避免删除:

 map.compute(rowIndex, (key, value) -> { // access row return null; }); 

所以这实际上是内存更少,但在我的场景中,使用synchronized进行简单的段锁定至少要快50%。

因为我们已经知道计算操作是primefaces的,所以值和synchronized必需的吗?

我确认在这种情况下不需要添加一个synchronized块,因为compute方法是primefaces地完成的,如ConcurrentHashMap#compute(K key, BiFunction remappingFunction)的Javadoc ConcurrentHashMap#compute(K key, BiFunction remappingFunction)自从Java 8以来已经添加BiFunction ,我引述:

尝试计算指定键及其当前映射值的映射(如果没有当前映射,则为null )。 整个方法调用以primefaces方式执行 。 其他线程在此映射上的某些尝试更新操作可能在计算进行时被阻止,因此计算应该简短,并且不得尝试更新此Map任何其他映射。

如果你让你的BiFunction总是返回null以primefaces地移除键,那么你试图用compute方法实现的东西可能是完全primefaces的,这样一切都将以primefaces方式完成。

 map.compute( rowIndex, (key, value) -> { // access row here return null; } ); 

这样,您将完全依赖ConcurrentHashMap的锁定机制来同步对行的访问。