2017-02-28 18 views
1

我需要一個AtomicLong值的多鍵映射。所以有些東西就像是番石榴AtomicLongMap,但是它支持多個密鑰。所以我的新的地圖應該能夠做到像:Java中Long或AtomicLong值的多鍵映射

MultiKeyAtomicLongMap<String, String> map = ... 

map.put("a", "A", 1L); 
map.put("a", "B", 2L); 
map.put("b", "C", 3L); 
.... 
map.get("a", "A"); // This should give me value 1L 
map.get("a"); // This should give me both mappings ["A", 1L] and ["B", 2L] 

所以上面的代碼只是爲了說明預期的行爲,但不是我想要什麼嚴格。

基本上我想要的是線程安全的多鍵映射,其中我的兩個鍵都是String,值爲long


編輯: 我很好的保持值Long,而不是AtomicLong,但我只是希望地圖是線程安全

+2

它看起來更像是你需要一個番石榴'表<字符串,字符串,AtomicLong>',不是? –

+0

@ControlAltDel它不會輕易檢索到一個單一的鍵(因爲OP是第二個'get'要求) –

+1

@JohnVint我認爲你的第一條評論是一個答案... – Eugene

回答

2

此答案由Jon Vint

基於註釋它看起來更像你需要一個番石榴表

番石榴表看起來像它給你你需要什麼,但沒有目前是一個線程安全實現。部分難點在於您需要管理地圖的地圖,並公開對值的地圖的訪問。

但是如果你很樂意同步訪問你自己的集合,我認爲一個番石榴表可以爲你提供你想要的功能,並且你可以添加線程安全。並添加你想要的公用事業公司的長期效用。

這一點比你問的比較抽象,但我認爲這給你需要的東西:

import com.google.common.base.MoreObjects; 
import com.google.common.collect.HashBasedTable; 
import com.google.common.collect.ImmutableMap; 
import com.google.common.collect.Table; 
import java.util.Map; 
import java.util.function.Function; 
import javax.annotation.concurrent.GuardedBy; 

/** 
* Provide something like the {@link com.google.common.util.concurrent.AtomicLongMap} but supporting 
* multiple keys. 
* 
* Should be able to put a value using two keys. And retrieve either a precise cell. Or retrieve a 
* collection of values. 
* 
* Created by James on 28/02/2017. 
*/ 
public class SynchronizedMultimap<Row, Column, Value> { 

    private final Object mutex = new Object(); 
    @GuardedBy("mutex") // All read and write access to delegate must be protected by mutex. 
    private final Table<Row, Column, Value> delegate = HashBasedTable.create(); 

    /** 
    * {@link Table#put(Object, Object, Object)} 
    * Associates the specified value with the specified keys. If the table 
    * already contained a mapping for those keys, the old value is replaced with 
    * the specified value. 
    * 
    * @return The old value associated with the keys or {@code null} if no previous value existed. 
    */ 
    public Value put(Row row, Column column, Value value) { 
     synchronized (mutex) { 
      return delegate.put(row, column, value); 
     } 
    } 

    /** 
    * {@link java.util.concurrent.ConcurrentMap#computeIfAbsent(Object, Function)} 
    * 
    * Checks the existing value in the table delegate by {@link Table#get(Object, Object)} and 
    * applies the given function, the function in this example should be able to handle a null input. 
    * 
    * @return The current value of the Table for keys, whether the function is applied or not. 
    */ 
    public Value compute(Row row, Column column, Function<Value, Value> function) { 
     synchronized (mutex) { 
      Value oldValue = delegate.get(row, column); 
      Value newValue = function.apply(oldValue); 
      if (newValue != null) { 
       delegate.put(row, column, newValue); 
       return newValue; 
      } 
      return oldValue; 
     } 
    } 

    /** 
    * {@link Table#get(Object, Object)} 
    * 
    * @return The value associated with the keys or {@code null} if no value. 
    */ 
    public Value get(Row row, Column column) { 
     synchronized (mutex) { 
      return delegate.get(row, column); 
     } 
    } 

    /** 
    * {@link Table#row(Object)} 
    * 
    * @return An immutable map view of the columns in the table. 
    */ 
    public Map<Column, Value> get(Row row) { 
     synchronized (mutex) { 
      // Since we are exposing 
      return ImmutableMap.copyOf(delegate.row(row)); 
     } 
    } 

    @Override 
    public String toString() { 
     // Even toString needs protection. 
     synchronized (mutex) { 
      return MoreObjects.toStringHelper(this) 
       .add("delegate", delegate) 
       .toString(); 
     } 
    } 
} 

更長久特定行爲:

/** 
* Provides support for similar behaviour as AtomicLongMap. 
* 
* Created by James on 28/02/2017. 
*/ 
public class SynchronizedLongMultimap<Row, Column> extends SynchronizedMultimap<Row, Column, Long> { 

    /** 
    * @return Adds delta to the current value and returns the new value. Or delta if no previous value. 
    */ 
    public long addAndGet(Row row, Column column, long delta) { 
     return compute(row, column, 
      (Long oldValue) -> (oldValue == null) ? delta : oldValue + delta); 
    } 

    /** 
    * @return Increments the current value and returns the new value. Or 1 if no previous value. 
    */ 
    public long increment(Row row, Column column) { 
     return compute(row, column, (Long oldValue) -> (oldValue == null) ? 1 : oldValue + 1); 
    } 

    /** 
    * @return Decrements the current value and returns the new value. Or -1 if no previous value. 
    */ 
    public long decrement(Row row, Column column) { 
     return compute(row, column, (Long oldValue) -> (oldValue == null) ? -1 : oldValue - 1); 
    } 
} 

添加單元測試,顯示邏輯

import static org.hamcrest.MatcherAssert.assertThat; 
import static org.hamcrest.core.IsEqual.equalTo; 

import com.google.common.collect.ImmutableMap; 
import org.junit.Test; 

/** 
* Test simple functionality of the Map is sound. 
* 
* Created by James on 28/02/2017. 
*/ 
public class SynchronizedLongMultimapTest { 

    private final SynchronizedLongMultimap<String, String> map = new SynchronizedLongMultimap<>(); 

    @Test 
    public void addAndGet_SingleCell() { 
     // add and get sets the initial value to the delta 
     assertThat(map.addAndGet("0", "0", 1), equalTo(1L)); 
     assertThat(map.addAndGet("0", "0", 1), equalTo(2L)); 
     assertThat(map.addAndGet("0", "0", 0), equalTo(2L)); 
     assertThat(map.addAndGet("0", "0", -2), equalTo(0L)); 
    } 
    @Test 
    public void addAndGet_RangeCells() { 
     // add and get sets the initial value to the delta 
     assertThat(map.addAndGet("0", "1", 123), equalTo(123L)); 

     // add and get sets the initial value to the delta 
     assertThat(map.addAndGet("1", "1", 42), equalTo(42L)); 
     // add and get adds the delta to the existing value 
     assertThat(map.addAndGet("1", "1", -42), equalTo(0L)); 
    } 

    @Test 
    public void increment() { 
     // increment sets the initial value to one 
     assertThat(map.increment("0", "0"), equalTo(1L)); 
     // then adds one each time it's called 
     assertThat(map.increment("0", "0"), equalTo(2L)); 
    } 

    @Test 
    public void decrement(){ 
     // decrement sets the initial value to -1 if no previous value 
     assertThat(map.decrement("apples", "bananas"), equalTo(-1L)); 
     // then decrements that 
     assertThat(map.decrement("apples", "bananas"), equalTo(-2L)); 
    } 

    @Test 
    public void get_PreviousValueIsNull() { 
     assertThat(map.get("toast", "bananas"), equalTo(null)); 
     // even if we ask again 
     assertThat(map.get("toast", "bananas"), equalTo(null)); 
    } 

    @Test 
    public void get_ProvidedByPut() { 
     assertThat(map.put("toast", "corn flakes", 17L), equalTo(null)); 
     // then we get what we put in 
     assertThat(map.get("toast", "corn flakes"), equalTo(17L)); 
    } 

    @Test 
    public void get_ColumnMap() { 
     // Expected behaviour from MultiKeyMap question 
     assertThat(map.put("a", "A", 1L), equalTo(null)); 
     assertThat(map.put("a", "B", 2L), equalTo(null)); 
     assertThat(map.put("b", "C", 3L), equalTo(null)); 

     // then we can get a single value 
     assertThat(map.get("a", "A"), equalTo(1L)); 
     // or a Map 
     assertThat(map.get("a"), equalTo(ImmutableMap.of("A", 1L, "B", 2L))); 
     // even if that Map only has a single value 
     assertThat(map.get("b"), equalTo(ImmutableMap.of("C", 3L))); 
    } 
} 
+0

感謝您的回答。想知道get方法Map get(Row row)和Value get(Row row,Column column)'還必須同步嗎?或者,因爲他們是讀取操作,他們可以鎖定自由嗎? – Learner

+0

@Learner是的,他們需要在這個例子中同步,否則結果將是不確定的。他們可以用另一種方式保護,使用ReadWriteLock而不是同步塊。 – James

+0

+1謝謝@James,你的意思是隻使用'ReadWriteLock'來獲取/讀取操作和使用代碼中顯示的同步進行寫入操作? – Learner

2

您可以使用Tables.synchronizedTable(Table table)Long的價值。這會給你一個線程安全的實現番石榴Table像以下:

Table<R, C, V> table = Tables.synchronizedTable(HashBasedTable.<R, C, V>create()); 
... 
Map<C, V> row = table.row(rowKey); // Needn't be in synchronized block 
... 
synchronized (table) { // Synchronizing on table, not row! 
    Iterator<Map.Entry<C, V>> i = row.entrySet().iterator(); // Must be in synchronized block 
    while (i.hasNext()) { 
    foo(i.next()); 
    } 
} 

注:不要在用戶需要在返回的表手動同步訪問任何收集時的重要建議錯過查看,因爲Table方法返回的那些集合視圖不同步。

+0

很好的答案@ kuldeep-jain,但只有在番石榴的快照構建。 – James

+0

+1哇,這很好,我希望它被釋放。我們有番石榴22.0版本發佈的日期嗎?我找不到那個。 – Learner

+0

是的,這在22.0-SNAPSHOT版本中可用。我不確定何時會發布。我認爲現在,你可以繼續採用@詹姆斯在他的回答中提出的方法。 –