ConcurrentModificationException仅在Java 1.8.0_45中

时间:2015-09-22 13:56:06

标签: java concurrency

我对这段代码有两个疑问:

import java.util.*;

public class TestClass {

    private static List<String> list;   
    public static void main(String[] argv) {

        list = generateStringList(new Random(), "qwertyuioasdfghjklzxcvbnmPOIUYTREWQLKJHGFDSAMNBVCXZ1232456789", 50, 1000);

//      Collections.sort(list, new Comparator<String>() {
//          public int compare(String f1, String f2) {
//              return -f1.compareTo(f2);
//          }
//      });

        for (int i = 0; i < 500; i++) {
            new MyThread(i).start();
         }

    }

    private static class MyThread extends Thread  {
        int id;
        MyThread(int id) { this.id = id; }
        public void run() {

            Collections.sort(list, new Comparator<String>() {
                public int compare(String f1, String f2) {
                    return -f1.compareTo(f2);
                }
            });

            for (Iterator it = list.iterator(); it.hasNext();) {
                String s = (String) it.next();
                try {
                    Thread.sleep(10 + (int)(Math.random()*100));
                }catch (Exception e) { e.printStackTrace(); }

                System.out.println(id+" -> "+s);
            }           
        }       
    }

    public static List<String> generateStringList(Random rng, String characters, int length, int size)
    {
        List<String> list = new ArrayList<String>();
        for (int j = 0; j < size; j++) {
            char[] text = new char[length];
            for (int i = 0; i < length; i++)
            {
                text[i] = characters.charAt(rng.nextInt(characters.length()));
            }
            list.add(new String(text));
        }
        return list;
    }
}

在java 1.8.0_45上运行此代码我得到java.util.ConcurrentModificationException

1)如果我在thread.start之前取消排序,为什么我也得到了异常?

2)为什么我只在java 1.8.0_45上获得异常?在1.6.0_45,1.7.0_79,1.8.0_5它工作正常。

4 个答案:

答案 0 :(得分:8)

@nbokmans已经确定了你获得该例外的一般原因。但是,这似乎与版本有关。我将填写为什么你在java 8.0_45中得到它,但不是1.6.0_45,1.7.0_79,1.8.0_5。

这是因为Collections.sort()在java 8.0_20中已更改。这里有一篇关于它的深入文章here。在新版本中,根据文章,排序是这样的:

public void sort(Comparator<? super E> c) {
  final int expectedModCount = modCount;
  Arrays.sort((E[]) elementData, 0, size, c);
  if (modCount != expectedModCount) {
    throw new ConcurrentModificationException();
  }
  modCount++;
}

就像文章解释的那样:

  

与旧的Collections.sort相反,此实现修改了   一旦列表出现,集合的modCount(上面的第7行)   排序,即使结构本身没有真正改变(仍然是   相同数量的元素。)

因此,即使集合已经排序,它也会进行内部更改,而在此更改之前,它并没有这样做。这就是你现在获得例外的原因。

实际的解决方法是不要同时使用多个线程对集合进行排序。你不应该这样做。

答案 1 :(得分:3)

当不允许进行此类修改时,检测到对象的并发(即在单独的线程中)修改的方法抛出ConcurrentModificationException。

您获得此异常的原因是您正在单独的线程中修改(排序)集合并对其进行迭代。

我引用了ConcurrentModificationException javadoc:

  

例如,一个线程通常不允许修改Collection而另一个线程正在迭代它。通常,在这些情况下,迭代的结果是不确定的。

Source

在您的代码中,您将启动500个线程,每个线程对列表进行排序和迭代。

在启动线程之前尝试对列表进行排序,并从MyThread的#run()中删除对Collections#sort的调用。

答案 2 :(得分:2)

使用Java 8,重新实现了Collections::sort方法以委派给List::sort方法。这样,如果对于给定的实现可能,则列表可以实现更有效的排序算法。例如,ArrayList可以使用其随机访问属性来实现比没有随机访问的LinkedList更有效的排序算法。

ArrayList::sort的当前实现显式检查修改,因为实现是在类中定义的,并且能够访问iternal属性。

在Java 8之前,Collections::sort方法必须实现实际的排序,并且无法委托。当然,实现可以访问特定列表的任何内部属性。更通用的排序实现如下:

public static <T> void sort(List<T> list, Comparator<? super T> c) {
    Object[] a = list.toArray();
    Arrays.sort(a, (Comparator)c);
    ListIterator i = list.listIterator();
    for (int j=0; j<a.length; j++) {
        i.next();
        i.set(a[j]);
    }
}

实现首先提取元素的副本,并将排序委托给Arrays::sort的实现。这不会导致观察到的异常,因为排序是在非共享元素副本上进行的。稍后,使用ListIterator

,根据排序的数组逐个元素更新元素

对于ArrayListArrayList及其迭代器会跟踪结构修改的数量,即更改列表大小的修改。如果迭代器和列表的数字不同,迭代器可以知道列表是在自己的迭代之外修改的。然而,它无法发现列表的元素在Collections::sort实现时发生了变化。

ArrayList的合同不允许同意修改合同。尽管在Java 8之前排序没有失败,但应用排序可能会导致错误的结果。从Java 8开始,这是第一次通过实现发现它。

答案 3 :(得分:0)

您正在获得此异常,因为您有单独的线程同时修改和迭代列表。

注释掉的排序不会导致问题。 CME是由线程内的排序和迭代引起的。由于您有多个线程排序和迭代,您将获得CME。这不依赖于Java版本。

看起来您的线程不需要修改列表,因此您可以在创建线程的循环之前执行一次排序,然后从线程中删除。