【问题标题】:HashMap synchronizationHashMap 同步
【发布时间】:2014-01-03 07:37:29
【问题描述】:

我有一个任务来同步HashMap 的方法put(K key, V value)。但它应该比synchronized(this)synchronized(table) 运行得更快。我写了这段代码:

public V put(K key, V value) {
    if (key == null) 
        return putForNullKey(value);

    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);

    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            synchronized(e) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

问题是当我通过不同的线程使用相同的键调用此方法时,可能会出现map.entrySet().size() &gt; 1 的情况。所以我的同步是错误的,但我不明白为什么。我怎样才能正确地做到这一点?

【问题讨论】:

  • 我无法使用它。这是一种教育任务。 A 必须同步普通的 HashMap,但它应该比 Collections.synchronizedMap(hashMap);
  • 你说的“情况,那个map.entrySet().size() > 1”和“同步出错”是什么意思,能详细点或者举例吗?
  • 如果table.length 没有改变(标准HashMap 确实改变了这一点),那么同步每个桶的访问(不仅仅是条目)将允许多个线程同时从不同的桶添加/读取时间。这将比锁定整个表有很大的改进,因为“好”(大小正确,具有均匀分布散列值的对象,..)散列表不会多次使用同一个存储桶,也不会在最坏的情况下使用几次。或者锁定存储桶的较小区域(称为条带锁定 AFAIK)。
  • 因为这是一项教育任务。也许您的教授甚至不知道他的问题的复杂性。请提供您的导师的解决方案。我真的很感兴趣。

标签: java multithreading hashmap synchronized


【解决方案1】:

一个问题是您的for 循环正在迭代一个可能发生变化的集合。

我真的会推荐ConcurrentHashMap,或者制作整个方法(或大部分方法)synchronized

【讨论】:

    【解决方案2】:

    因为你的 e 可以在它进入同步块之前改变, 完整的表可以在同步块的迭代之前和期间发生变化。

    问题是锁定表条目。 因此,将完整的方法声明为同步可能会更好。

    使用信号量(请参阅 Java 并发 API)以确保对您的表进行原子访问。

    由于评论而更新:

    为您的应用程序提供缓存机制。引入一个额外的列表(队列,缓存) 它会在您的表或地图被阻止时收集所有传入的更改。 所以你的申请仍然可以继续。直到桌子不再被你挡住 将缓存条目添加到您的表中。

    一个答案,但只是一个建议。

    【讨论】:

    • 虽然您已经发现了问题,但同步方法的解决方案会减慢速度,OP 正在寻找更快的解决方案。
    • @Sotirios Delimanolis 我认为这是一个更快的解决方案。至少是一个解决方案的猜测。
    • 我们应该始终尝试仅在关键部分附近进行同步。在整个方法上同步可能会导致更多的瓶颈。
    • @Sotirios Delimanolis。你有这个问题的最佳解决方案吗?我认为它相当困难。而且也很难证明它是否真的更快,因为无法检查建议
    • 我没有解决方案,你是对的,这将很难测试。
    【解决方案3】:

    问题是当我通过不同的线程使用相同的键调用此方法时,可能会出现 map.entrySet().size() > 1 的情况。所以我的同步是错误的,但我不明白为什么。

    我不能 100% 确定我理解错误情况,但我发现您尝试同步时存在许多问题,这些问题会导致竞争条件。

    例如,如果两个线程尝试同时向HashMap 添加相同的键,它们将循环遍历存储桶并且找不到条目。然后他们俩都将尝试将条目添加到表中。这可能导致两个条目具有相同的键,甚至损坏表。

    我还假设addEntry(...) 可以更改存储桶数组,这可能会导致所有元素的重新散列。因此,一个线程可能会导致存储桶发生变化,而另一个线程正在使用过期的数组。这可能会导致条目丢失或再次损坏表。

    我怎样才能正确地做到这一点?

    这是一个不平凡的练习。您可以做的一件事是使用ReadWriteLock,这样至少您可以进行查找而不会阻止写入。但是如果你正在向映射中写入一个新条目,由于上面提到的对桶数组的更改,你可能必须对其进行排他锁。

    【讨论】:

    • 感谢您的有趣回答 +1
    猜你喜欢
    • 2010-11-20
    • 1970-01-01
    • 2017-05-18
    • 1970-01-01
    • 1970-01-01
    • 2012-11-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多