2015-09-22 31 views
4

我有两个问题,关于这个代码:ConcurrentModificationException的用Java 1.8.0_45

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上它工作正常。

回答

7

@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行以上)一旦列表已被 排序,即使结构本身没有真正改变(仍然是 相同数量的元素)。

因此即使集合已经排序,它也会进行内部更改,而在更改之前它不会执行内部更改。这就是为什么你现在得到一个例外。

+0

感谢您的解释,任何建议的修复? – ronnyfm

+1

@ronnyfm修复不是要同时使用多个线程对集合进行排序。你不应该那样做。 – eis

3

当这种修改不允许时,已经检测到并发(即在单独的线程中)修改对象的方法抛出了ConcurrentModificationException。

你得到这个异常的原因是你在一个单独的线程中修改(排序)集合并迭代它。

我从ConcurrentModificationException的javadoc中引述:

例如,它不是一般允许一个线程修改集合,而另一个线程上进行迭代。一般来说,在这些情况下迭代的结果是不确定的。

Source

在代码中,你开始500个线程每个排序和遍历列表。

尝试在开始线程之前排序列表,并从MyThread的#run()中移除对集合的调用#排序。

+0

我知道为什么我得到这个例外。我在问为什么它是Java版本依赖的,为什么我仍然得到它,如果我在开始线程之前对列表进行排序 – Alvins

+0

对不起,我不确定为什么你的代码似乎在1.8.0_45之前工作正常。我尽力回答我的能力。 – nbokmans

0

你会得到这个异常,因为你有单独的线程同时修改和迭代列表。

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

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

+0

您可以备份它不依赖于Java版本的声明吗?我使用代码OP进行了测试,实际上对于Java 7来说,似乎没有任何例外。 – eis

+0

@eis - 尝试设置测试,使其不断运行。我怀疑你在运行期间可能只是幸运。 –

+1

@JohnMcClean我不这么认为 - 测试似乎是一致的,似乎有所有这一切的实际解释。添加了我自己的答案。 – eis

2

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

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

在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根据排序后的数组逐个元素更新元素。

对于ArrayList,所述ArrayList和其迭代跟踪结构修饰,即该改变列表的大小的修改的数量。如果这些数字与迭代器和列表不同,则迭代器可以知道该列表在其迭代之外被修改。然而,它不能发现列表中的元素被改变了,因为Collections::sort的实现发生了。

ArrayList的合同确实不允许在合同中同时修改。尽管在Java 8之前排序没有失败,但应用排序可能会导致错误的结果。自Java 8以来,这是第一次,这是由实现发现的。

相关问题