我有一个Java类,它同时被很多线程访问,我想确保它是线程安全的。这个类有一个私有字段,它是一个字符串到字符串列表的Map。我将Map实现为ConcurrentHashMap,以确保gets和puts是线程安全的:
public class ListStore {
private Map<String, List<String>> innerListStore;
public ListStore() {
innerListStore = new ConcurrentHashMap<String, List<String>>();
}
...
}
因此,给定Map的get和put是线程安全的,我关心的是存储在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中的列表内容。当此方法迭代它时,将抛出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);
}
如果这是修改存储在map中的底层列表的唯一方法,并且没有公共方法返回对map的引用或map中的条目,那么我可以同步列表本身的迭代吗?add()的这种实现是否足够?
8条答案
按热度按时间polkgigr1#
您可以在listToSearch(“synchronized(listToSearch){...}”)。确保创建列表时没有竞争条件(使用innerListStore。putIfAbsent来创建它们)。
70gysomp2#
你可以只在listToSearch上同步,没有理由在任何人只使用一个条目的时候锁定整个Map。
但是请记住,您需要在列表中的任何修改位置进行同步!同步迭代器不会自动阻止其他人执行add()或其他操作,如果你向他们传递了对未同步列表的引用。
最安全的做法是将同步列表存储在Map中,然后在迭代时锁定它们,并记录何时返回对列表的引用,如果用户迭代,则必须在该列表上同步。在现代JVM中,当没有实际的争用发生时,同步是相当便宜的。当然,如果您从不让对某个列表的引用脱离您的类,您可以在内部使用更精细的梳理来处理它。
或者,您可以使用线程安全列表,如使用快照迭代器的CopyOnWriteArrayList。您需要什么样的时间点一致性是我们无法为您做出的设计决策。javadoc还包括对性能特性的有益讨论。
cld4siwp3#
看起来我需要同步这个方法的全部内容,因为如果另一个方法改变了innerListStore中的列表内容。当此方法迭代它时,将抛出ConcurrentModificationException。
其他线程是访问List本身,还是仅通过
ListStore
公开的操作?其他线程调用的操作是否会导致存储在Map中的a List的内容被更改?还是只在Map中添加/删除条目?
如果不同的线程可能导致对相同List示例的更改,则只需要同步对存储在Map中的List的访问。如果线程仅被允许从Map添加/移除List示例(即,例如,如果用户不需要同步(例如,改变Map的结构),则同步不是必需的。
4uqofj5v4#
如果存储在Map中的列表是不抛出CME的类型(例如CopyOnWriteArrayList),则可以随意迭代
如果你不小心的话,这可能会引入一些种族
yc0p9oo05#
如果Map已经是线程安全的,那么我认为同步listToSearch应该可以。我不是100%,但我认为它应该工作
a2mppw5e6#
你可以用Guava的另一个抽象概念
请注意,这将在整个Map上同步,因此它可能对您没有那么有用。
clj7thdc7#
由于除了
boolean listEntryExists(String listName, String listEntry)
方法之外,您没有为列表Map提供任何客户端,我想知道您为什么要存储列表?这个结构看起来更自然地是Map<String, Set<String>>
,listEntryExists
应该使用contains
方法(List
上也可用,但O(n)到列表的大小):现在,contains调用可以封装任何您希望它封装的内部并发协议。
对于
add
,您可以使用同步 Package 器(简单,但可能很慢),或者如果写入与读取相比不频繁,则利用ConcurrentMap.replace
实现您自己的写时复制策略。例如,使用GuavaImmutableSet
:这现在是一个完全线程安全的无锁结构,其中并发读取器和写入器不会互相阻塞(对底层ConcurrentMap实现的无锁性取模)。这个实现在写操作中确实有一个O(n),而你原来的实现在读操作中是O9n)。同样,如果你主要是阅读而不是写作,这可能是一个很大的胜利。
xytpbqjk8#
它可以用更少的编码来完成。下面是一个示例,它是并发压力测试Jcstress enter link description here的一部分