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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12736142/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 10:01:20  来源:igfitidea点击:

Threads and Concurrent Modification Exception working with a list

javamultithreadingexceptionconcurrency

提问by Paul Blundell

I know this is a daft questions but cant work out how to fix this, I have not had much experience with using threads before.

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

Below should create first of all a timer which will execute the command output.write(mylist) every 10 seconds which will simply output the contents of mylist.

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

Secondly it loops through about 3 lists I have and for each them creates a thread which will continue to loop through getting the next word in the list. Please note: this is stripped down version and not complete so please don't comment on the arraylist / list but rather the error itself.

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

There is a concurrent modification exception happening frequently but not all the time when it tries to do output.write(). I am guessing this is because one of the other threads are currently saving something to mylist? How would I go about fixing this?

有一个并发修改异常经常发生,但在尝试执行时并非总是发生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();
    }
}

回答by Gray

There is a concurrent modification exception happening frequently but not all the time when it tries to do output.write...

有一个并发修改异常经常发生,但在尝试执行时并非总是发生output.write......

The problem is (I assume) that the output.write(...)method is iterating through your myListat the same time as another thread is calling myList.addWord(curr);. This is not allowed unless myListis a concurrent collection.

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

How would I go about fixing this?

我将如何解决这个问题?

You will need to synchronize on myListevery time you are accessing it -- in this case when you are outputting it or adding a word to it.

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

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

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

In this case, because output.write(mylist)is probably iterating through the list, you cannot use the Collections.synchronizedList(...)method because the iterators need to be synchronized by the caller.

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

If this is some high-performance method that is called tons of times then you could also use a ConcurrentLinkedQueuebut that is a queue, not a list obviously. ConcurrentSkipListis another option although that is a heavier data structure.

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

回答by Giulio Franco

Just like you said, this is happening because Java iterators are fail-fast, and will fail if the collection is concurrently modified. One easy solution is to protect accesses to your list with synchronized, but this will cause your threads to stop, waiting for the output to complete.

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

One maybe smarter solution is to use some concurrent implementation of List. Since your list is often modified, CopyOnWriteArrayListis not the solution you want. If it doesn't require too many modifications, you might use a ConcurrentLinkedDequeor a LinkedBlockingDeque, which are backed by a linked list, and do notimplement List, so they don't have a getmethod (which you don't seem to be using). Anyway, the iterators returned by these collections are not fail-fast, but weakly-consistent (which means they will 'see' the collection how it was when the iterator was created, or reflect some of the later modifications).

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

Another solution may be to use a concurrent collection (ConcurrentLinkedDeque) and a ReadWriteLock, but in a more original way. Your writer threads would use the read lock (thus being able to write concurrently). You printer thread would acquire the write lock (thus temporarily blocking the other threads), make a copy of the collection into a non-concurrent collection, release the write lock and finally print its local copy.

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

A definitely smarter (and probably faster) choice would be to assign a different non-concurrent list to each thread. So you would have a list of non-concurrent lists, each assigned to a single thread. When all the threads are done, you can join all your lists into a single one. Moreover, each of the lists should be protected by a lock. When your printer thread wants to print, it iterates through the lists, locking them one at a time, making a copy of it, releasing the lock, and then printing it.

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

The reason why I insist in making copies is that it's faster, much faster than printing I/O. If your worker threads will have to wait for the printer thread to print the list, all your computation will slow down.

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

PS: even if the only wrong thing you can see is the exception, ArrayLists are not thread safe. If you add elements to the same list from multiple threads, you will lose some elements (unless some weirder exception occurs).

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

回答by Oakeybloke

Or just add synchronizedto the deceleration of run(). Alternatively enclose the contents of the method in a synchronized{...}block, as long as the sleeping/waiting is in the 'synchronized zone', i.e. where only one thread is allowed to operate at a time.

Anyone reading this for something they're stuck on shoud read through:
http://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html
It will be sound investment in time;)

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

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