可直接访问的数据结构Java

时间:2013-04-25 09:28:57

标签: java data-structures concurrency

我有以下情况:

  1. 一种只能扩展的数据结构(我只有 在尾巴中添加东西)
  2. 我需要能够跟踪我已经拥有的元素 看到了(我有一个索引,理想情况下我希望能够开始 再次从这个特定的元素遍历列表)
  3. 我希望读取永远不会阻塞,并且添加 新元素只能锁定队列的尾部而不是 整个队列
  4. 这是一个由多个线程严重修改的结构。

    最好的数据结构是什么?

    的ArrayList 即可。这对于能够直接访问使用索引看到的最后一个元素是理想的,但它会导致并发修改异常。我可以让它同步,但是想避免锁定(或者除了最后一个元素之外的任何锁定,因为它是唯一可能有并发写入以添加新元素的元素)

    的ConcurrentLinkedQueue 即可。这将解决我的并发问题,但问题是我必须存储迭代的当前位置而不是整数索引。这有一个问题,它返回一个弱一致的迭代器,它不能保证返回自创建迭代器以来已添加到列表中的新对象(source:javadoc)

    ConcurrentHashMap ,索引为键。这样做的好处是我可以直接访问与正确索引相对应的数据,但是有一个问题就是没有“getNext”运算符可以让我有效地遍历索引,索引+ 1等元素

    向量这将解决我的大多数问题,即允许不会引发并发修改异常并允许直接访问的内容。但是,鉴于所有方法都是同步的,与arraylists相比,性能较差。鉴于我只想扩展结构,而不是在中间插入记录,我不愿意采用这种重量级解决方案,其中读取也会受到性能损失(而且,考虑到我的用例,元素的索引)从来没有真正改变,因此不需要同步不是尾部的读取

    自定义数据结构:保存我想要存储的对象数组和指向此数组尾部的指针(最后一个元素集),插入新对象时,锁定尾部和尾巴指向的物体。当对象超过其当前大小时,进行锁定调整大小操作。

    什么是最佳策略/任何其他更有效的实施?

8 个答案:

答案 0 :(得分:11)

CopyOnWriteArrayList结构可以解决您的问题(java.util.concurrent)。

  • CopyOnWriteArrayList是线程安全的,因为所有的变异操作都是通过创建列表副本来实现的。

  • 避免了ConcurrentModificationException的问题,因为迭代时数组不会改变。所谓的snapshot style iterator在创建迭代器时使用对数组状态的引用。

  • 如果您的阅读次数多于写入次数,请使用CopyOnWriteArrayList,否则请使用Vector

  • Vector为每个操作引入了一个小的同步延迟,当CopyOnWriteArrayList有更长的写入延迟(由于复制)但没有读取延迟时。

  • Vector在迭代时需要显式同步(因此无法同时执行写操作),CopyOnWriteArrayList没有。

答案 1 :(得分:4)

这听起来很像你需要一个distruptor或简单的单词锁定自由队列。我希望我能在这里添加一个例子,但我昨天才开始研究它。我也可以告诉你它是如何工作的,或者你可以在这里阅读更好的解释:

一般的想法是,它完全无锁,它只使用CAS寄存器(在java AtomicXXX中)。我只是爱上了这个想法。

LMAX

答案 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符合你的三点。

  1. 可以使用方法add扩展向量,该方法在末尾添加元素 列表。
  2. Vectors使用方法get(index)来检索特定索引处的具体元素。
  3. 向量是线程安全的:java Vector and thread safety http://docs.oracle.com/javase/7/docs/api/java/util/Vector.html

答案 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作为“活动”部分,并使用某种管理器定期将项目移动到“您看过的项目”列表。