Java中具有Long或AtomicLong值的多键映射

时间:2017-02-28 21:05:52

标签: java multithreading collections guava atomic

我需要一个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我很好,但我只想让地图线程安全

2 个答案:

答案 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)));
    }
}