1

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

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

其次,它循环遍历我拥有的大约 3 个列表,并为每个列表创建一个线程,该线程将继续循环获取列表中的下一个单词。请注意:这是精简版,并不完整,因此请不要评论数组列表/列表,而是评论错误本身。

有一个并发修改异常经常发生,但在它尝试做的时候并不总是发生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();
    }
}
4

3 回答 3

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(...)方法,因为迭代器需要由调用者同步。

如果这是一种被多次调用的高性能方法,那么您也可以使用 aConcurrentLinkedQueue但那是一个队列,而不是一个列表。 ConcurrentSkipList是另一种选择,尽管这是一个较重的数据结构。

于 2012-10-04T21:25:36.977 回答
1

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

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

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

一个绝对更聪明(并且可能更快)的选择是为每个线程分配一个不同的非并发列表。所以你会有一个非并发列表的列表,每个列表都分配给一个线程。完成所有线程后,您可以将所有列表合并为一个。此外,每个列表都应该受到锁的保护。当您的打印机线程想要打印时,它会遍历列表,一次锁定一个,制作一份副本,释放锁定,然后打印它。

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

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

于 2013-08-14T21:13:58.543 回答
0

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

任何阅读此内容的人都应该阅读:
http
://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html 这将是对时间的合理投资;)

于 2013-08-14T20:36:40.627 回答