【问题标题】:thread safe LinkedHashMap without Collections.synchronized没有 Collections.synchronized 的线程安全 LinkedHashMap
【发布时间】:2023-03-14 14:19:01
【问题描述】:

我使用的是 LinkedHashMap 并且环境是多线程的,所以这个结构需要是线程安全的。在特定事件期间,我需要阅读整个地图推送到数据库并清除所有内容。

大多数情况下,此映射只会发生写入。此地图的条目限制为 50 个。

我正在使用 Oracle MAF,但它没有可用的 Collections.syncronizedMap。那么,我需要在同步块中放入哪些内容以确保写入和读取不会打到我 concurrentModificationException 等

几个要求:

  1. 我需要让它像一个循环队列一样,所以重写 LinkedHashMap 的 removeEldestEntry 方法。
  2. 我需要保留订单

【问题讨论】:

  • 每次调用 hasmap 上的方法时,例如 getput,都需要用同步包围它。如果您连续有多个呼叫,您可以用一个同步将它们全部包围。发布您的一些代码。
  • 在写入数据库时​​不要同步地图。制作一个副本(在 synchronized{} 中),将副本写入 db。
  • 您看到了什么好处?我的地图最多可以容纳 50 个条目。而且我相信制作副本与迭代它所花费的时间相同。

标签: java multithreading data-structures concurrency oracle-maf


【解决方案1】:

那么,我需要在同步块中放入哪些内容以确保写入和读取不会影响我 concurrentModificationException 等

所有方法调用都应该在同步块中。

使用迭代器是一个棘手的问题,因为您必须在迭代器的生命周期内持有锁。例如

// pre Java 5.0 code
synchronized(map) { // the lock has to be held for the whole loop.
    for(Iterator iter = map.entrySet().iterator(); iter.hashNext(); ) {
         Map.Entry entry = iter.next();
         String key = (String) entry.getKey();
         MyType value = (MyType) entry.getValue();
         // do something with key and value.
    }
}

【讨论】:

  • 所以我像 if(diagMap != null && diagMap.size() > 0){ Set keys = diagMap.keySet(); for(日期键:键){ 字符串值 = diagMap.get(key); dPath.append(key).append(DELIM).append(value);它不会在 for 循环之后释放迭代器吗?
【解决方案2】:

在多线程环境中,大多数LinkedHashMap 操作都需要synchronization,即使是那些看起来像get(key)get(key) 这样的纯操作实际上也会改变一些内部节点。最简单的方法是使用Collections.synchronizedMap

Map<K,V> map = Collections.synchronizedMap(new LinkedHashMap<>());

现在,如果它不可用,您可以轻松添加它,因为它只是一个简单的decorator 围绕映射 synchronize 的所有操作。

class SyncMap<T,U> implements Map<T,U>{
  SyncMap<T,U>(LinkedHashMap<T,U> map){
   ..
  }
  public synchronized U get(T t){
    ..
  }
}

【讨论】:

  • 我需要对所有的 LinkedHashMap 操作都这样做吗?
  • 好的,我认为最好在gets和puts周围使用同步块
【解决方案3】:

如果您使用的是 java 1.5 或更高版本,您可以使用java.util.concurrent.ConcurrentHashMap

这是在多线程环境中使用的最有效的 Map 实现。

它还添加了一些方法,例如putIfAbsent,对于地图上的原子操作非常有用。

来自 java 文档:

检索操作(包括get)一般不会阻塞,所以可能 与更新操作重叠(包括放置和删除)。检索 反映最近完成的更新操作的结果 坚持他们的发病。 用于聚合操作,例如 putAll 和 明确并发检索可能反映插入或删除 一些条目

所以请确认这是您对班级的期望。


如果您的地图只有 50 条记录并且需要用作循环队列,为什么要使用地图?使用其中一种队列实现不是更好吗?


如果您需要使用 LinkedHashMap,请使用以下内容:

Map m = Collections.synchronizedMap(new LinkedHashMap());

来自javadocLinkedHashMap

请注意,此实现不同步。如果多个线程 同时访问链接的哈希映射,并且至少访问其中一个线程 从结构上修改地图,它必须在外部同步。 这通常是通过在某些对象上同步来完成的 自然地封装了地图。如果不存在这样的对象,则地图 应该使用 Collections.synchronizedMap 方法“包装”。这 最好在创建时完成,以防止意外不同步 访问地图:

 Map m = Collections.synchronizedMap(new LinkedHashMap(...));

https://docs.oracle.com/javase/7/docs/api/java/util/LinkedHashMap.html

【讨论】:

  • 我相信它不支持重写 removeEldestEntry 方法使其表现得像一个循环队列
  • 正如我在帖子中所说,我的环境中没有可用的 Collections.synchronizedMap,它使用受限的 jdk 配置文件
  • 所以我想你只能用同步或使用自定义数据结构来包围你的代码
猜你喜欢
  • 2012-10-20
  • 2016-01-16
  • 1970-01-01
  • 1970-01-01
  • 2012-05-27
  • 2016-04-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多