【问题标题】:Directly accessible data structure Java可直接访问的数据结构 Java
【发布时间】:2013-04-19 03:13:37
【问题描述】:

我有以下情况:

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

这是一个被多个线程大量修改的结构。

最好的数据结构是什么?

数组列表。如果能够直接访问使用索引看到的最后一个元素,这将是理想的,但它会导致并发修改异常。我可以使其同步,但希望避免锁定(或除最后一个元素之外的任何锁定,因为它是唯一可能存在并发写入以添加新元素的锁定)

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

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

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

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

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

【问题讨论】:

  • 您可以使用 java.util.Vector 代替 ArrayList。这将解决您的同步问题。
  • +1 为您的研究工作。
  • @user1018513 “我需要能够跟踪我已经看到的元素”是什么意思。你能解释一下吗?
  • 请结合“多线程大量修改”来说明“只能扩展”。这是否意味着它会快速而永久地增长?另外——你试图用那个数据结构解决什么问题?这应该是发布-订阅者队列的变体还是其他东西?
  • @user1018513 在遍历时您是希望始终随机访问还是只希望随机访问(即:开始遍历,执行其他操作,从我离开的地方继续遍历)。

标签: java data-structures concurrency


【解决方案1】:

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

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

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

  • 如果你的读多于写,使用CopyOnWriteArrayList,否则使用Vector

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

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

【讨论】:

  • 鉴于问题中提供的详细信息,最好扩展您的答案以解释为什么它符合标准。
  • “具有同步解的向量”?
  • 有大量写入,我怀疑写时复制将是一个好技术。但是,与往常一样,您只有在测量时才会知道。
【解决方案2】:

你必须使用单一的数据结构吗?如果您使用两个 - 一个用于列表的“活动”部分,一个用于“您已经看到的项目”列表怎么办?您可以将 Vector 用于“活动”部分,并使用某种管理器定期将项目移动到“您已看到的项目”列表中。

【讨论】:

    【解决方案3】:

    这听起来很像您需要一个干扰器或简单地说无锁队列。我希望我可以在这里添加一个示例,但我昨天才开始研究它。我也可以告诉你它是如何工作的,或者你可以在这里阅读更好的解释:

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

    LMAX

    【讨论】:

      【解决方案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();
        }
      }
      

      【讨论】:

        【解决方案5】:

        我要提供ConcurrentSkipListSet,因为:

        1) 它是并发的。

        2) 这是Set

        3) 它也是NavigableSet,因此也是SortedSet

        这为您提供了很大的灵活性,其中大部分您可能不需要。但除了“你不能添加已经存在的项目”(我不知道这是一个问题还是一个恩惠)之外,它似乎满足了你的所有要求。

        【讨论】:

          【解决方案6】:

          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 ;
          

          您需要做的就是定义可配置对象并相应地编写构造函数。

          希望这对您有所帮助。

          【讨论】:

            【解决方案7】:

            正如sk2212所说,我认为java.util.Vector符合你的三点。

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

            【讨论】:

            • 请补充一下,由于同步,它会很慢
            • 我担心使用向量是因为所有方法都是同步的,与数组列表相比,它们会产生不可忽略的性能开销。因为我实际上永远不会修改数组的内部结构(除了扩展它)似乎是一个重量级的解决方案。
            • 查看此链接以查看 Vector 与 ArrayList 的性能javacodegeeks.com/2010/08/…
            【解决方案8】:

            数组列表。如果能够直接访问使用索引看到的最后一个元素,这将是理想的,但它会导致并发修改异常。我可以使其同步,但希望避免锁定(或除最后一个元素之外的任何锁定,因为它是唯一可能存在并发写入以添加新元素的锁定)

            您可以使用临时列表来放置要添加的对象,当读取的内容解除阻塞时,您可以将 tmpList 的内容添加到 ArrayList。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2022-10-23
              • 1970-01-01
              • 2013-02-07
              • 2018-06-18
              • 2010-11-22
              • 1970-01-01
              • 2016-12-24
              相关资源
              最近更新 更多