簡體   English   中英

使用盡可能少的鎖在 Java 中並發字節數組訪問

[英]Concurrent byte array access in Java with as few locks as possible

我正在嘗試減少分段數據的鎖定對象的內存使用量。 在此處此處查看我的問題。 或者假設您有一個字節數組,並且每 16 個字節可以(反)序列化為一個對象。 讓我們稱其為行長為 16 字節的“行”。 現在,如果您從寫入器線程修改這樣的行並從多個線程讀取,則需要鎖定。 如果您的字節數組大小為 1MB (1024*1024),這意味着 65536 行和相同數量的鎖。

這有點太多了,而且我需要更大的字節數組,我想將其減少到與線程數大致成正比的程度。 我的想法是創建一個

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

你認為有可能使這個線程安全嗎?

我有一種感覺,這看起來與concurrentMap.compute構造非常相似(例如,請參閱此答案),或者我什至可以使用這種方法嗎?

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 中找不到這個

更新:我在這里發現了一個與 userIds 而不是 rowIndices 的非常相似的問題,請注意,該問題包含一個示例,其中包含幾個問題,例如缺少final 、在try-finally子句中調用lock以及缺少縮小地圖。 似乎還有一個用於此目的的庫 JKeyLockManager ,但我認為它不是線程安全的

更新 2:解決方案似乎非常簡單,因為 Nicolas Filotto 指出了如何避免刪除:

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

因此,這確實是較少內存密集,但在我的場景中,使用synchronized的簡單段鎖定至少快 50%。

因為我們已經知道計算操作是原子的,所以值和synchronized必要的嗎?

我確認在這種情況下不需要添加synchronized塊,因為compute方法是原子地完成的,如ConcurrentHashMap#compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)的 Javadoc 中所述ConcurrentHashMap#compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)Java 8以來已添加BiFunction ,我引用:

嘗試計算指定鍵及其當前映射值的映射(如果沒有當前映射,則為null )。 整個方法調用以原子方式執行 其他線程在此映射上嘗試的一些更新操作可能會在計算過程中被阻塞,因此計算應該簡短且簡單,並且不得嘗試更新此Map任何其他映射。

如果您讓BiFunction始終返回null以原子方式刪除鍵,那么您嘗試使用compute方法實現的目標可能是完全原子的,這樣一切都將以原子方式完成。

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

通過這種方式,您將完全依賴ConcurrentHashMap的鎖定機制來同步您對行的訪問。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM