我有以下情况:
这是一个由多个线程严重修改的结构。
最好的数据结构是什么?
的ArrayList 即可。这对于能够直接访问使用索引看到的最后一个元素是理想的,但它会导致并发修改异常。我可以让它同步,但是想避免锁定(或者除了最后一个元素之外的任何锁定,因为它是唯一可能有并发写入以添加新元素的元素)
的ConcurrentLinkedQueue 即可。这将解决我的并发问题,但问题是我必须存储迭代的当前位置而不是整数索引。这有一个问题,它返回一个弱一致的迭代器,它不能保证返回自创建迭代器以来已添加到列表中的新对象(source:javadoc)
ConcurrentHashMap ,索引为键。这样做的好处是我可以直接访问与正确索引相对应的数据,但是有一个问题就是没有“getNext”运算符可以让我有效地遍历索引,索引+ 1等元素
向量这将解决我的大多数问题,即允许不会引发并发修改异常并允许直接访问的内容。但是,鉴于所有方法都是同步的,与arraylists相比,性能较差。鉴于我只想扩展结构,而不是在中间插入记录,我不愿意采用这种重量级解决方案,其中读取也会受到性能损失(而且,考虑到我的用例,元素的索引)从来没有真正改变,因此不需要同步不是尾部的读取
自定义数据结构:保存我想要存储的对象数组和指向此数组尾部的指针(最后一个元素集),插入新对象时,锁定尾部和尾巴指向的物体。当对象超过其当前大小时,进行锁定调整大小操作。
什么是最佳策略/任何其他更有效的实施?
答案 0 :(得分:11)
CopyOnWriteArrayList结构可以解决您的问题(java.util.concurrent)。
CopyOnWriteArrayList
是线程安全的,因为所有的变异操作都是通过创建列表副本来实现的。
避免了ConcurrentModificationException
的问题,因为迭代时数组不会改变。所谓的snapshot style iterator
在创建迭代器时使用对数组状态的引用。
如果您的阅读次数多于写入次数,请使用CopyOnWriteArrayList
,否则请使用Vector
。
Vector
为每个操作引入了一个小的同步延迟,当CopyOnWriteArrayList
有更长的写入延迟(由于复制)但没有读取延迟时。
Vector
在迭代时需要显式同步(因此无法同时执行写操作),CopyOnWriteArrayList
没有。
答案 1 :(得分:4)
这听起来很像你需要一个distruptor或简单的单词锁定自由队列。我希望我能在这里添加一个例子,但我昨天才开始研究它。我也可以告诉你它是如何工作的,或者你可以在这里阅读更好的解释:
一般的想法是,它完全无锁,它只使用CAS寄存器(在java AtomicXXX中)。我只是爱上了这个想法。
答案 2 :(得分:4)
考虑到这一点,我找到了与@MissingNumber相同的解决方案。
使用ConcurrentHashMap作为后备数据结构:
要通过索引添加随机访问,请使用AtomicInteger维护索引并将其作为检索映射值的键。
public class ConcurrentListMap {
private final ConcurrentHashMap<Integer, Object> backingMap;
private final AtomicInteger index;
public ConcurrentListMap() {
backingMap = new ConcurrentHashMap();
index = new AtomicInteger(0);
}
public int append(final Object value) {
final int newIndex = index.incrementAndGet();
backingMap.put(newIndex, value);
return newIndex;
}
public Object get(final int entry) {
return backingMap.get(entry);
}
public int getTailIndex() {
return index.get();
}
}
答案 3 :(得分:1)
正如sk2212所说,我认为java.util.Vector
符合你的三点。
add
扩展向量,该方法在末尾添加元素
列表。get(index)
来检索特定索引处的具体元素。答案 4 :(得分:1)
ConcurrentHashMap 可以解决您的问题,但您需要做更多工作才能解决这个问题。
喜欢遵循伪代码。
Map<Integer , ConfiguredObject > myMap = new ConcurrentHashMap<Integer,ConfiguredObject >();
class ConfiguredObject
{
YourObject Object;// the object which you want to map for map[key];
ConfiguredObject Next;//the object which you want to map for map[key+1];
ConfiguredObject Previous;//the object which you want to map for map[key-1];
YourObject NextObject;
YourObject PrevObject;
}
所以这应该解决你所有的问题。
并发框架需要注意。
建立索引键是您的索引。
迭代,如果您有索引,可以使用此代码
myMap.get(key).Next ;
myMap.get(key).Previous ;
您需要做的就是相应地定义可配置对象和编写构造函数。
希望这对你有所帮助。
答案 5 :(得分:0)
的ArrayList。这对于能够直接访问使用索引看到的最后一个元素是理想的,但它会导致并发修改异常。我可以让它同步,但是想避免锁定(或者除了最后一个元素之外的任何锁定,因为它是唯一可能有并发写入以添加新元素的元素)
您可以使用临时List来放置要添加的对象,当读取的内容解除阻塞时,您可以将tmpList的内容添加到ArrayList。
答案 6 :(得分:0)
我要提供ConcurrentSkipListSet
,因为:
1)它是并发的。
2)这是Set
。
3)它也是NavigableSet
,因此也是SortedSet
。
这为您提供了很多灵活性,其中大部分都是您可能不需要的。但除了“你不能添加已经存在的物品”(我不知道这是一个问题,还是一个福音),它似乎满足了你的所有要求。
答案 7 :(得分:0)
您是否必须使用单一数据结构?如果你使用两个 - 一个用于列表的“活动”部分,一个用于“你看过的项目”列表怎么办?您可以使用Vector作为“活动”部分,并使用某种管理器定期将项目移动到“您看过的项目”列表。