我知道这是一个愚蠢的问题,但无法解决如何解决这个问题,我以前没有太多使用线程的经验。
下面应首先创建一个定时器,它将每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();
}
}
答案 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
不是您想要的解决方案。
如果不需要太多修改,您可以使用由链表支持的ConcurrentLinkedDeque
或LinkedBlockingDeque
,并执行不实施{{1}因此,他们没有List
方法(您似乎没有使用它)。无论如何,这些集合返回的迭代器不是快速失败的,而是弱一致的(这意味着它们将“看到”集合在创建迭代器时的样式,或者反映出后来的一些修改)。
另一个解决方案可能是使用并发集合(get
)和ReadWriteLock,但是以更原始的方式。您的编写器线程将使用读锁定(因此能够并发写入)。您的打印机线程将获取写锁(从而暂时阻止其他线程),将该集合的副本复制到非并发集合中,释放写锁并最终打印其本地副本。
一个明智的(可能更快)选择是为每个线程分配一个不同的非并发列表。因此,您将拥有一个非并发列表的列表,每个列表都分配给一个线程。完成所有线程后,您可以将所有列表合并为一个。而且,每个列表都应该由锁保护。当您的打印机线程要打印时,它会遍历列表,一次锁定一个列表,复制它,释放锁定,然后打印它。
我坚持复制的原因是它比打印I / O更快,更快。如果你的工作线程必须等待打印机线程打印列表,那么你的所有计算都会变慢。
PS:即使你能看到的唯一错误是异常,ConcurrentLinkedDeque
也不是线程安全的。如果从多个线程向同一列表添加元素,则会丢失一些元素(除非发生一些更奇怪的异常)。
答案 2 :(得分:0)
或者只需将synchronized
添加到run()
的减速度。或者,只要睡眠/等待处于“同步区域”,即一次只允许一个线程操作的情况下,将方法的内容包含在synchronized{...}
块中。
任何读这篇文章的人都应该通过以下方式阅读:
http://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html
这将是合理的投资;)