我需要一个AtomicLong
值的多键地图。所以像 guava 的AtomicLongMap,但支持多个键的东西。所以我的新地图应该能够像:
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
我很好,但我只想让地图线程安全。
答案 0 :(得分:3)
您可以将Tables.synchronizedTable(Table table)与Long
值一起使用。这将为您提供 guava 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());
}
}
注意:不要错过用户在访问其任何集合视图时手动同步返回的表所需的重要建议,因为{返回的那些集合视图{1}}方法未同步。
答案 1 :(得分:2)
此答案基于Jon Vint
的评论看起来更像是你需要一个番石榴表
Guava表看起来像是为您提供所需,但目前还没有线程安全的实现。部分困难在于您需要管理地图地图,并公开对价值地图的访问。
但是如果您乐意同步访问自己的集合,我认为guava表可以为您提供所需的功能,您可以添加线程安全性。并为inc / dec Long添加所需的实用程序。
这比你提出的要抽象得多,但我认为这可以满足你的需求:
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();
}
}
}
对于Long特定行为:
/**
* 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)));
}
}