【问题标题】:Iterating and modifying java queue at the same time同时迭代和修改java队列
【发布时间】:2018-10-08 04:13:19
【问题描述】:

我有一个列表和一个指向列表元素的指针。有时我需要:

  • 在队列末尾追加一个值
  • 从队列头部移除一个值
  • 使指针前进到列表中的下一个值

即:

  • 从 WRITE 的角度来看,它是一个队列。
  • 从 READ 的角度来看,它是一个列表。

如果我使用普通的迭代器,我会在修改队列时得到 ConcurrentModificationException;如果我使用 ListIterator,我只能在迭代器位置删除/添加值。

有什么标准的数据结构可以用来实现这一点吗?

【问题讨论】:

  • 你是在不同线程同时读写吗?
  • 可以分享一下你目前的实现代码吗?
  • @assylias 我在单线程上
  • 参见java.util.LIstIterator,以及返回一个的各种方法。
  • @Jack 那么它应该是可以解决的。你能分享一个抛出异常的代码示例吗?

标签: java iterator queue


【解决方案1】:

您可以使用ConcurrentLinkedQueue。它允许同时修改和迭代,因为它包含了必要的同步机制。

下面的 sn-p 显示了一个工作示例,其中有 3 个线程访问同一个队列而没有问题: 1.迭代和输出元素 2.偶尔添加新元素 3. 偶尔删除输出的元素

package test;

import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;

public class QueueListTest
{
    // private static final Queue<Integer> numbers = new ConcurrentLinkedQueue<>();

    public static void main(String[] args)
    {
        final Queue<Integer> numbers = new ConcurrentLinkedQueue<>();

        final AtomicInteger insert = new AtomicInteger(0);
        final AtomicInteger output = new AtomicInteger();

        for(int j = 0; j < 100; j++)
        {
            numbers.add(insert.getAndIncrement());
        }

        // print 1 number every 100ms
        Thread t1 = new Thread() {
            public void run()
            {
                Iterator<Integer> iter = numbers.iterator();
                while(iter.hasNext())
                {

                        int first = numbers.peek();
                        int size = numbers.size();
                        int last = first + size - 1;
                        int current = iter.next();

                        System.out.println("list from " + first + " to " + last + " @ " + current);
                        output.set(current);

                    try
                    {
                        Thread.sleep(100);
                    }
                    catch(InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                }
            }
        };

        // add 5 number every 500ms
        Thread t2 = new Thread() {
            public void run()
            {
                while(true)
                {
                    for(int j = 0; j < 5; j++)
                    {
                        numbers.add(insert.getAndIncrement());
                    }
                    try
                    {
                        Thread.sleep(500);
                    }
                    catch(InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                }
            }
        };

        // remove all printed numbers every 1000ms
        Thread t3 = new Thread() {
            public void run()
            {
                while(true)
                {
                    try
                    {
                        Thread.sleep(1000);
                    }
                    catch(InterruptedException e)
                    {
                        e.printStackTrace();
                    }

                        int current = output.intValue();

                        while(numbers.peek() < current)
                            numbers.poll();
                }
            }
        };

        t1.start();
        t2.start();
        t3.start();

        try
        {
            t1.join();
            t2.join();
            t3.join();
        }
        catch(InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

因为队列是“链接的”,它应该处理在恒定时间内迭代、添加和删除,因此是您可以使用的最有效的实现。

【讨论】:

  • 是的,这可能是最适合我需要的结构。同时,考虑到单线程的限制,我认为它可以存在一个没有同步开销的结构,因此效率更高。好可惜
  • 老实说,您可能想要一个 ConcurrentLinkedDeque 代替(因为您将在开头和结尾都插入)。我只是想知道性能开销。
【解决方案2】:

不是真的。问题是,没有一种结构可以有效地满足您的需求。

  • 您可以使用ArrayList,在开始处插入后迭代索引并保持更新的当前索引(递增 1),但在开始处插入效率不高
  • 你不能使用LinkedList,因为它不会暴露当前的Node

您最好的选择可能是来自 Apache Commons Collections 的 CursorableLinkedList (https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/list/CursorableLinkedList.html)

【讨论】:

  • 当然,您可以在使用LinkedList 时通过使用Iterator 公开当前的Node。唯一的问题是并发。因此您可以使用同步实例(=> 参见上面的解决方案stackoverflow.com/a/50062786/4090157
  • 可惜没有这样的结构。在有单个线程访问它的约束下,它可能很容易实现(并且同时有用)。谢谢你的链接,我去看看
【解决方案3】:

在遍历项目之前创建列表的副本。

或者你有其他限制吗?

【讨论】:

  • 列表可以很大;我正在寻找一个有效的解决方案。此外,从头重新启动迭代器可能比复制列表更有效
猜你喜欢
  • 1970-01-01
  • 2012-12-08
  • 2017-12-05
  • 1970-01-01
  • 2014-09-17
  • 1970-01-01
  • 1970-01-01
  • 2019-04-04
相关资源
最近更新 更多