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

标签 java multithreading collections guava atomic

我需要一个具有 AtomicLong 值的多键映射。所以类似于 AtomicLongMap 的东西guava 但它支持多个键。所以我的新 map 应该能够像这样:

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 但我只是希望 map 是线程安全的

最佳答案

此答案基于 Jon Vint 的评论

It looks more like you need a Guava Table

Guava 表看起来可以满足您的需求,但目前还没有线程安全的实现。部分困难在于您需要管理 map 中的 map ,并公开对值 map 的访问权限。

但如果您乐于同步访问自己的集合,我认为 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)));
    }
}

关于java - Java 中具有 Long 或 AtomicLong 值的多键映射,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42518713/

相关文章:

Javascript 及其单线程

java - 让等待线程跳过剩余的等待/继续

c - 大量互斥体的性能影响

java - 为什么 java.util.Set 没有 get(int index)?

java - CAdES 数字签名

Java线程切换

java - Spring 可缓存 - 使用 SpEL 过滤掉空集合

collections - Java 8 中分组的反转

java - 使用 Java 按 HashMap 类中的值排序

java - 将数据从 Perl 脚本交换到 Java Android 应用程序的最简单安全方法