【问题标题】:Concurrent two-way map in javajava中的并发双向映射
【发布时间】:2012-07-13 19:12:36
【问题描述】:

我正在编写用于文本处理的代码,如果我先将字符串转换为整数,事情会变得快很多。为此,我创建了一个 Dictionary 类,每次我看到一个新字符串时,我都会给它一个索引,并保留两个映射,一个从 string 到 int,一个从 int 到 string,所以我可以轻松地查找两种方式.代码如下:

class Dictionary {
    private Map<String, Integer> map;
    private Map<Integer, String> reverse_map;
    private int nextIndex;

    public Dictionary() {
        map = new HashMap<String, Integer>();
        reverse_map = new HashMap<Integer, String>();
        nextIndex = 1;
    }

    public int getIndex(String string) {
        if (!map.containsKey(string)) {
            map.put(string, nextIndex);
            reverse_map.put(nextIndex, string);
            nextIndex++;
        }
        return map.get(string);
    }

    public String getString(int index) {
        // getIndex is always called first, so we don't need to check anything
        return reverse_map.get(index);
    }
}

这在我的单线程代码中运行良好。但现在我想给这个多个线程以加快速度,我不知道该怎么做。我想过使用 ConcurrentHashMap,但我不确定putIfAbsent 是否能保证我不会两次使用索引。我不想使用 Collections.synchronizedMap,因为这个字典在线程之间被非常频繁地访问,所以我可能不会比使用单个线程更好,因为它在每次读取和写入时都会阻塞。有没有办法让这个工作?

【问题讨论】:

  • hmm,所以您只需要插入/获取,并且密钥是自行生成的(即增量?),如果是这样,即使同时并发也可以使算法更快

标签: java concurrency hashmap concurrenthashmap


【解决方案1】:

最简单的方法是标记您的两种方法(getIndexgetStringsynchronized。看看你得到什么样的加速。也许就够了。

要使用ConcurrentHashMap,你可以试试这个:

private AtomicInteger nextIndex;
public int getIndex(String string) {
    Integer n = map.get(string);
    if (n == null) {
        int idx = nextIndex.getAndIncrement();
        n = map.putIfAbsent(string, idx);
        if (n != null) return n;
        reverse_map.put(idx, string);
        return idx;
    }
    return n;
}

如果两个线程同时插入相同的字符串,这可能偶尔会跳过索引,但不会经常发生。

【讨论】:

  • 这看起来可行。我从AtomicInteger 不知道getAndIncrement,我担心可能会使用相同的索引来引用两个不同的字符串。如果我总是getAndIncrement,我可能会跳过索引,就像你说的那样,但永远不会有重复的索引,这真的很糟糕。谢谢!
  • n 在您将其用作第二张地图中的键时几乎可以保证为nullputIfAbsent 返回键下的前一个值,您刚刚检查为 null
  • 对,如果n == null,使用idx作为reverse_map中的key,否则使用n
  • @mattg 实际上(请参阅我的解决方案),否则 不要使用任何东西 - 跳过该步骤。
【解决方案2】:

并发解决方案的问题是原子性。以下是我的想法:

private final ConcurrentMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();
private final ConcurrentMap<Integer, String> reverse_map = new ConcurrentHashMap<Integer, String>();
private final AtomicInteger nextIndex = new AtomicInteger(1);

public int getIndex(String string) {
  Integer i = map.get(string);
  if (i == null) {
    final Integer newI = nextIndex.getAndIncrement();
    i = map.putIfAbsent(string, newI);
    if (i == null) {
      reverse_map.put(newI, string);
      return newI;
    }
  }
  return i;
}

这有一个非常良性的故障模式:一些索引将被闲置。

请注意,我无条件放入第二张地图,因为此时我知道我负责手头的字符串。

【讨论】:

  • CHM 也使用锁,我会说可以做非锁阅读器的 2way 地图。永远不需要它,尝试做起来也不够有趣。
  • 我认为唯一真正需要原子的部分是第一个插入;第二个只是重复,如果我能保证我从第一个上的putIfAbsent 得到正确的结果,事情应该没问题。我的问题是nextIndex++map.putIfAbsent 之间的相互作用。您可能可以做到,只是我对线程编程的了解还不够,无法确保做对。
  • @bestsss CHM 不只锁定写入吗?
  • @mattg 我明白了,我错过了那部分。 putIfAbsent 返回键下的前一个值,因此如果它返回一个非空值,您就知道其他线程已经在您处理该字符串之前。在这种情况下,只需跳过第二步。我认为可能是这样,但必须考虑更多。
  • 当然,是的,只写新的 CHM v8.x 实际上更好,Cliff Click 的并发哈希表版本是完全无锁的
猜你喜欢
  • 1970-01-01
  • 2010-11-14
  • 2010-11-07
  • 1970-01-01
  • 1970-01-01
  • 2016-04-19
  • 2020-05-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多