2015-09-22 52 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以來,這是第一次,這是由實現發現的。

相關問題