0

我需要确保多个线程不会同时尝试访问同一个资源。我有一堆这些资源,所以我想为每个资源(而不是一个全局锁)拥有一个单独的锁对象,这样线程就不会不必要地阻塞彼此。

ConcurrentMap.putIfAbsent()Eddie在https://stackoverflow.com/a/659939/82156中为此提供了一个很好的解决方案。

// From https://stackoverflow.com/a/659939/82156
public Page getPage(Integer id) {
  Page p = cache.get(id);
  if (p == null) {
    synchronized (getCacheSyncObject(id)) {
      p = getFromDataBase(id);
      cache.store(p);
    }
  }
}

private ConcurrentMap<Integer, Integer> locks = new ConcurrentHashMap<Integer, Integer>();

private Object getCacheSyncObject(final Integer id) {
  locks.putIfAbsent(id, id);
  return locks.get(id);
}

但是,该实现的一个问题是哈希图将无限增长。有没有办法在线程完成时删除哈希键而不会搞砸并发?

4

2 回答 2

1

如果您知道特定线程是请求特定锁的最后一个线程,那么您可以使用locks.remove(id, id). 否则,您需要对两个不容易同步的事件进行原子更新。

如果您释放然后删除锁,另一个线程可能会抓住您释放的锁,而另一个线程会使新的锁对象忘记原始锁对象。在这种情况下,您最终可能会同时调用两个getFromDataBase(id)线程cache.store(p)。如果您删除然后释放,则可能有另一个线程在等待您释放旧锁,而新线程已经建立了新锁。可能会发生同样的碰撞。

本质上,您不能在不向系统添加新锁的情况下原子地释放锁并将其从 HashMap 中删除。在这种情况下,您要么最终得到一个全局锁——这阻碍了哈希特定锁定的加速——要么你需要为每个页面添加一个额外的锁,它本身就会有相同的删除问题。或者,如果您可以访问 HashMap 的内部结构,您可以尝试一些棘手的存储桶锁暴露给您的更高级别的逻辑,尽管我不建议您尝试实现这样的事情。

限制哈希图大小的一种解决方案是改用固定大小的映射。用大数字 (10,000) 修改您的整数 ID,并将修改后的数字用作唯一的锁 ID。两个散列冲突并需要为不同页面使用相同锁的可能性非常小,并且您对锁消耗的内存有一个硬上限。在这种情况下,您实际上不需要 HashMap,因为您可以将 Integer 锁定对象预分配到静态 const 数组中,并直接从 id 对象请求 mod 哈希。

此外,您应该小心在锁之外进行缓存读取,除非缓存本身受到同时读写的保护,因为一个线程可能正在写入,而另一个线程则从问题中指定的代码方式读取。

于 2013-04-01T23:38:51.183 回答
1

Pyrce 让我意识到并不要求每个资源都有自己的锁定对象。相反,我可以使用一个包含 100 个锁对象的池,这些对象可以在资源之间共享。这样,锁定对象的集合就不会无限增长,但我仍然可以获得我希望通过删除单个全局锁定获得的大部分并发优势。

这意味着我不再需要使用 a ConcurrentHashMap,而可以只使用一个急切初始化的简单数组。

// getPage() remains unchanged
public Page getPage(Integer id) {
  Page p = cache.get(id);
  if (p == null) {
    synchronized (getCacheSyncObject(id)) {
      p = getFromDataBase(id);
      cache.store(p);
    }
  }
}

private int MAX_LOCKS = 100;
private Object[] locks = new Object[MAX_LOCKS];

{
    for(int i=0; i<locks.length; ++i)
        locks[i] = new Object();
}


private Object getCacheSyncObject(final Integer id) {  
  return locks[ id % MAX_LOCKS ];
}
于 2013-04-04T01:00:19.870 回答