使用列表的线程和并发修改异常

时间:2012-10-04 21:19:51

标签: java multithreading exception concurrency

我知道这是一个愚蠢的问题,但无法解决如何解决这个问题,我以前没有太多使用线程的经验。

下面应首先创建一个定时器,它将每10秒执行一次命令output.write(mylist),这将只输出mylist的内容。

其次它循环通过我拥有的大约3个列表,并且每个列表创建一个线程,它将继续循环获取列表中的下一个单词。 请注意:这是精简版并且没有完整,所以请不要评论arraylist / list而不是错误本身。

有一个并发修改异常经常发生,但并非总是在它尝试output.write()时发生。我猜这是因为其他一个线程目前正在保存mylist的东西?我该如何解决这个问题?

    Runnable timer = new Runnable() {
        public void run() {
            try {
                while (true) {
                    Thread.sleep(10000);
                    output.write(mylist);
                }
            } catch (InterruptedException iex) {}
        }
    };
    Thread timerThread = new Thread(timer);
    timerThread.start();

    for (final ValueList item : list) {

        Runnable r = new Runnable() {
            public void run() {
                try {
                    while (true) {

                        char curr = item.getNext();

                         mylist.addWord(curr);
                    }
                } catch (InterruptedException iex) {}
            }
        };

        Thread thr = new Thread(r);
        thr.start();
    }
}

3 个答案:

答案 0 :(得分:9)

  

经常发生并发修改异常,但并非所有时间都尝试output.write ...

问题是(我假设)output.write(...)方法在另一个线程调用myList的同时迭代myList.addWord(curr);。除非myList是并发集合,否则不允许这样做。

  

我将如何解决这个问题?

每次访问时,您都需要在myList上进行同步 - 在这种情况下,当您输出或向其添加单词时。

  synchronized (myList) {
      output.write(mylist);
  }
  ...

  synchronized (myList) {
      myList.addWord(curr);
 }

在这种情况下,因为output.write(mylist)可能正在遍历列表,所以不能使用Collections.synchronizedList(...)方法,因为调用者需要同步迭代器。

如果这是一种被称为吨次的高性能方法,那么你也可以使用ConcurrentLinkedQueue,但这是一个队列,显然不是列表。 ConcurrentSkipList是另一种选择,尽管这是一个更重的数据结构。

答案 1 :(得分:1)

就像你说的那样,这种情况正在发生,因为Java迭代器是快速失败的,如果同时修改了集合,它将会失败。 一个简单的解决方案是使用synchronized保护对列表的访问,但这会导致线程停止,等待输出完成。

一个更聪明的解决方案是使用List的一些并发实现。 由于您的列表经常被修改,CopyOnWriteArrayList不是您想要的解决方案。 如果不需要太多修改,您可以使用由链表支持的ConcurrentLinkedDequeLinkedBlockingDeque,并执行实施{{1}因此,他们没有List方法(您似乎没有使用它)。无论如何,这些集合返回的迭代器不是快速失败的,而是弱一致的(这意味着它们将“看到”集合在创建迭代器时的样式,或者反映出后来的一些修改)。

另一个解决方案可能是使用并发集合(get)和ReadWriteLock,但是以更原始的方式。您的编写器线程将使用读锁定(因此能够并发写入)。您的打印机线程将获取写锁(从而暂时阻止其他线程),将该集合的副本复制到非并发集合中,释放写锁并最终打印其本地副本。

一个明智的(可能更快)选择是为每个线程分配一个不同的非并发列表。因此,您将拥有一个非并发列表的列表,每个列表都分配给一个线程。完成所有线程后,您可以将所有列表合并为一个。而且,每个列表都应该由锁保护。当您的打印机线程要打印时,它会遍历列表,一次锁定一个列表,复制它,释放锁定,然后打印它。

我坚持复制的原因是它比打印I / O更快,更快。如果你的工作线程必须等待打印机线程打印列表,那么你的所有计算都会变慢。

PS:即使你能看到的唯一错误是异常,ConcurrentLinkedDeque也不是线程安全的。如果从多个线程向同一列表添加元素,则会丢失一些元素(除非发生一些更奇怪的异常)。

答案 2 :(得分:0)

或者只需将synchronized添加到run()的减速度。或者,只要睡眠/等待处于“同步区域”,即一次只允许一个线程操作的情况下,将方法的内容包含在synchronized{...}块中。

任何读这篇文章的人都应该通过以下方式阅读: http://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html
这将是合理的投资;)