我对这段代码有两个疑问:
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它工作正常。
答案 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而另一个线程正在迭代它。通常,在这些情况下,迭代的结果是不确定的。
在您的代码中,您将启动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
对于ArrayList
,ArrayList
及其迭代器会跟踪结构修改的数量,即更改列表大小的修改。如果迭代器和列表的数字不同,迭代器可以知道列表是在自己的迭代之外修改的。然而,它无法发现列表的元素在Collections::sort
实现时发生了变化。
ArrayList
的合同不允许同意修改合同。尽管在Java 8之前排序没有失败,但应用排序可能会导致错误的结果。从Java 8开始,这是第一次通过实现发现它。
答案 3 :(得分:0)
您正在获得此异常,因为您有单独的线程同时修改和迭代列表。
注释掉的排序不会导致问题。 CME是由线程内的排序和迭代引起的。由于您有多个线程排序和迭代,您将获得CME。这不依赖于Java版本。
看起来您的线程不需要修改列表,因此您可以在创建线程的循环之前执行一次排序,然后从线程中删除。