Java并发与列表映射

问题描述

| 我有一个Java类,该线程可以同时被很多线程访问,并希望确保它是线程安全的。该类具有一个私有字段,该私有字段是字符串到字符串列表的映射。我已将Map实施为ConcurrentHashMap,以确保获取和放置线程安全:
public class ListStore {

  private Map<String,List<String>> innerListStore;

  public ListStore() {
    innerListStore = new ConcurrentHashMap<String,List<String>>();
  }
  ...
}
因此,鉴于Map的获取和放置是线程安全的,我关心的是Map中存储的列表。例如,考虑以下方法,该方法检查商店中给定列表中是否存在给定条目(为了简洁起见,我省略了错误检查):
public boolean listEntryExists(String listName,String listEntry) {

  List<String> listToSearch = innerListStore.get(listName);

  for (String entryName : listToSearch) {
    if(entryName.equals(listEntry)) {
      return true;
    }
  }

  return false;
}
似乎我需要同步此方法的全部内容,因为如果在此方法对其进行迭代时,如果另一个方法更改了innerListStore.get(listName)上列表的内容,则会抛出ConcurrentModificationException。 那是正确的吗?如果是这样,我应该在innerListStore上进行同步还是在本地listToSearch变量上进行同步? 更新:感谢您的答复。听起来我可以在列表本身上进行同步。有关更多信息,这是add()方法,可以在另一个线程中运行listEntryExists()方法的同时运行该方法:
public void add(String listName,String entryName) {

  List<String> addTo = innerListStore.get(listName);
  if (addTo == null) {
    addTo = Collections.synchronizedList(new ArrayList<String>());
    List<String> added = innerListStore.putIfAbsent(listName,addTo);
    if (added != null) {
      addTo = added;
    }
  }

  addTo.add(entryName);
}
如果这是唯一修改存储在映射中的基础列表的方法,并且没有公共方法返回对映射的引用或映射中的条目,那么我是否可以同步列表本身的迭代,并且add()的这种实现是否足够?     

解决方法

        您可以仅在listToSearch上进行同步,没有理由在任何人仅使用一个条目的情况下锁定整个地图。 不过请记住,您需要在列表被修改的所有地方进行同步!同步迭代器不会自动阻止其他人执行add()或其他操作(如果您向其他人传递了对未同步列表的引用)。 将同步列表存储在Map中,然后在进行迭代时将其锁定,然后在返回对列表的引用时进行文档记录,这是最安全的,如果用户对列表进行迭代,则必须对其进行同步。当没有实际争用发生时,同步在现代JVM中非常便宜。当然,如果您从不让对其中一个列表的引用离开您的类,则可以使用更精细的梳子在内部进行处理。 或者,您可以使用线程安全列表,例如使用快照迭代器的CopyOnWriteArrayList。您需要哪种时间一致性是我们无法为您做出的设计决策。 Javadoc还包括有关性能特征的有用讨论。     ,        您可以在listToSearch上进行同步(\“ synchronized(listToSearch){...} \”)。确保没有竞争条件创建列表(使用innerListStore.putIfAbsent创建列表)。     ,           似乎我需要同步此方法的全部内容,因为如果在此方法对其进行迭代时,如果另一个方法更改了innerListStore.get(listName)上列表的内容,则会抛出ConcurrentModificationException。 其他线程是否正在访问列表本身,或者仅通过“ 3”公开了操作? 其他线程调用的操作是否会导致Map中存储的List的内容被更改?还是仅在地图中添加/删除条目? 如果不同的线程可能导致对相同List实例的更改,则仅需要同步对Map中存储的List的访问。如果仅允许线程从Map添加/删除List实例(即更改Map的结构),则不需要同步。     ,        如果存储在地图中的列表的类型不抛出CME(例如,CopyOnWriteArrayList),则可以随意进行迭代 如果您不小心的话,这可能会带来一些比赛     ,        如果Map已经是线程安全的,那么我认为同步listToSearch应该可行。我不是100%,但我认为应该可以
synchronized(listToSearch)
{

}
    ,        您可以使用番石榴的另一种抽象 请注意,这将在整个地图上同步,因此它对您可能没有用。     ,除了
boolean listEntryExists(String listName,String listEntry)
方法外,您还没有为列表提供任何客户端,我想知道为什么您要存储列表?这个结构似乎更自然地是一个
Map<String,Set<String>>
,而
listEntryExists
应该使用
contains
方法(也可以在ѭ9ava上使用,但是O(n)等于列表的大小):
public boolean listEntryExists(String name,String entry) {
  SetString> set = map.get(name);
  return (set == null) ? false : set.contains(entry;
}
现在,contains调用可以封装您想要的任何内部并发协议。 对于
add
,您可以使用同步包装器(简单,但可能很慢),或者如果写入次数少于读取次数,则可以使用
ConcurrentMap.replace
来实现自己的写时复制策略。例如,使用番石榴
ImmutableSet
public boolean add(String name,String entry) {
  while(true) {
    SetString> set = map.get(name);
    if (set == null) {
      if (map.putIfAbsent(name,ImmutableSet.of(entry))
        return true
      continue;
    }
    if (set.contains(entry)
      return false; // no need to change,already exists
    Set<String> newSet = ImmutableSet.copyOf(Iterables.concat(set,ImmutableSet.of(entry))        
    if (map.replace(name,set,newSet)
      return true;
  }
}
现在这是一个完全线程安全的无锁结构,在该结构中,并发的读取器和写入器不会互相阻塞(对基础ConcurrentMap实现的无锁性进行模运算)。此实现在其写入中确实具有O(n),而您的原始实现为O9n)。再次,如果您主要是阅读而不是主要是写作,那么这可能是一个大胜利。     

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...