2012-06-14 2 views

답변

8

편집 :

내 초기 응답이 '예'만 @JohnVint 올바르게 지적으로, 그것은 ArrayListSystem.arrayCopy(...)를 사용하여 배열을 복제하는 커버 아래부터 ConcurrentModificationException을하지 않습니다. 끝에있는 코드 스 니펫을 참조하십시오.

이 복사본을 만들 때 다른 스레드가 요소 배열을 변경하고있는 것이 문제입니다. System.arraycopy(...)이 원시 코드에서 수행되었으므로 IndexOutOfBoundsException, 초기화되지 않은 배열 값 또는 일종의 원시 메모리 액세스 예외가 발생할 수 있습니다.

이러한 경쟁 조건으로부터 보호하고 ArrayList을 백업하는 요소 배열이 적절하게 최신 상태인지 확인하기 위해 메모리 장벽을 설정할뿐 아니라 업데이트 및 복사 중에 목록에서 동기화해야합니다.


public ArrayList(Collection<? extends E> c) { 
    elementData = c.toArray(); 
    ... 
} 

// ArrayList 
public Object[] toArray() { 
     return Arrays.copyOf(elementData, size); 
} 

// Arrays 
public static <T,U> T[] copyOf(U[] original, int newLength, 
    Class<? extends T[]> newType) { 
    ... 
    System.arraycopy(original, 0, copy, 0, 
     Math.min(original.length, newLength)); 
} 

// System 
public static native void arraycopy(Object src, int srcPos, 
    Object dest, int destPos, int length); 
+0

'는'동기화되지 않는 한 Collections.synchronizedList'는 반복과 (목록)가 CME를 얻을 수 있습니다 { }' –

+0

Yikes, @Peter, 감사합니다. 나는 대답을 고쳐 줄 것이다. – Gray

+0

목록 크기가 1000000 인 CME를 실제로 구현할 수 없었습니다. 백업 배열이 복사 배열 의 System.arraycopy를 통해 복사되는 특별한 경우에는 현재 배열의 스냅 샷을 가져 옵니까? 목록 크기 조정에 영향을 줍니까? – Sushant

1

당신은 당신이 여기에서 무엇을하고 있는지에 대해 생각해야합니다. list의 클래스가 스레드로부터 안전하지 않은 경우 newList과 함께 list을이 코드로 완전히 파기 할 수 있습니다. - CME가 가장 문제가되지 않습니다. (나는 CME를 던지지 않는 클래스를 제안하지만이 경우 CME는 이다.) 참고 :이 코드는 테스트하기가 어렵다. 모든 실패 사이에 아무런 문제없이 실행되는 0 ~ 10 억 개 정도의 위치에 도달하게됩니다. 실패는 엄청나게 많고 이성적인 설명을 넘어서는 경향이 있지만 매우 미묘합니다.

가장 빠른 해결 방법은 list을 잠그는 것입니다. 당신은 그것을 확실하게하고 싶습니다 어디에나 그것은 사용됩니다; 목록을 실제로 잠그지 않으면 액세스하려는 코드 블록이 잠겨 있습니다. 의 모든을 잠글 수 있습니다. 단점은 새 목록이 생성되는 동안 다른 스레드를 차단한다는 것입니다. 이것은 정말로가는 길입니다. 그러나, "목록이 매우 거대합니다"라고 말하면 성능에 대해 우려 할 수 있으므로 계속하겠습니다 ...

newList을 불변으로 처리하면이 작업을 수행 할 가치가 있습니다. 한 번 만들어지면 자주 사용합니다. 수많은 코드가 일치하지 않을 염려없이 과 같은 문제없이 newList을 동시에 읽을 수 있습니다. 그러나 초기 창작과 함께 여전히 유예가 있습니다.

다음 단계는 list을 java.util.ConcurrentLinkedQueue로 만드는 것입니다. (당신이 뭔가 더 좋아할 필요가있는 경우에는 동시 맵이 있고 세트가 있습니다.)이 기능은 많은 스레드가 추가되고 삭제되는 동안 많은 스레드가 그것을 읽을 수 있으며 항상 작동합니다. 이 포함되어 있다고 생각할 수는 없지만 반복자는 무한 루프가되지 않습니다 (list이 java.util.LinkedList 인 경우 발생할 수 있음). 이렇게하면 다른 스레드가 다른 스레드에서 작동하는 동안 newList이 하나의 코어에서 생성됩니다.

다운 사이드 : list이 ArrayList 인 경우 동시 클래스로 전환하는 작업이 다소있을 수 있습니다. 동시 클래스는 더 많은 메모리를 사용하며 일반적으로 ArrayList보다 느립니다. 훨씬 더 중요 : list의 내용이 일치하지 않을 수 있습니다. (사실, 이미이 문제가 있습니다.) 다른 스레드에서 동시에 A와 B 항목을 추가하거나 제거 할 수 있으며 둘 중 하나 또는 둘 모두가 newList에있을 것으로 예상합니다. 거기에 있으십시오. 반복자는 추가되거나 제거 된 후부터 다른 반복자가 오기 전에 통과합니다. (싱글 코어 머신은 그다지 문제가 없습니다.) 그러나 list이 이미 일정하고 흐트러진 플럭스로 생각된다면, 이것은 당신이 원하는 것일 수도 있습니다.

다른 부작용 : (ArrayList 및 HashTable과 같이) 큰 배열 및 해당 항목을 사용하는 경우주의해야합니다. 항목을 제거 할 때 공간을 적게 차지하지 않으므로 대부분의 메모리를 차지하는 작은 데이터로 많은 수의 큰 배열로 끝날 수 있습니다.

항목을 추가 할 때 항목을 추가 할 때 오래된 배열을 해제하고 더 큰 새로운 배열을 할당합니다 ( ). 그러면 조각난 여유 메모리가 생깁니다. 즉, 여유 메모리는 대부분 오래된 배열의 덩어리가됩니다. 그 중 어떤 것도 다음 할당에 사용하기에 충분하지 않습니다. 가비지 컬렉터는이 모든 것을 조각 모음하려고 시도하지만, 많은 작업이 필요하며 GC는 여유 블록을 재정렬하는 데 시간을 들이지 않고 메모리 부족 예외를 발생시켜 최대 메모리 블록을 확보 할 수 있습니다. 방금 요청했습니다. 따라서 메모리의 10 % 만 사용 중일 때 메모리 부족 오류가 발생합니다.

배열은 가장 빠른 것이지만, 큰 배열은주의해서 사용해야합니다. 각 할당 및 무료 알고 있어야합니다. 공간을 다시 할당하지 않도록 적절한 초기 크기를 지정하십시오. (당신이 C 프로그래머 인 척해라.) GC에 친절하라. 큰 목록을 자유롭게 만들고 무료로 크기를 조정해야한다면 LinkedList, TreeMap, ConcurrentLinkedQueue 등의 링크 된 클래스를 사용하는 것이 좋습니다. 이들은 메모리를 약간만 사용하며 GC는이를 선호합니다.

0

나는 @Gray가 말한 것을 테스트하기 위해 몇 가지 코드를 만들었습니다. 배열 목록에서 제거하는 동안 복사 생성자를 사용하면 생성 된 목록에 null 요소가 생성됩니다. 당신은 잘못된 항목의 수는 다음 코드 조각에서 지속적으로 증가로 이것을 볼 수 있습니다 : 심지어

public static void main(String[] args) { 

    final int n = 1000000; 
    final int m = 100000; 
    final ArrayList<String> strings = new ArrayList<String>(n); 

    for(int i=0; i<n; i++) { 
     strings.add(new String("abc")); 
    } 


    Thread creatorThread = new Thread(new Runnable() { 
     @Override 
     public void run() { 
      ArrayList<String> stringsCme = new ArrayList<String>(strings); 
      int wrongEntries = 0; 
      for(int i=0; i<m; i++) { 
       stringsCme = new ArrayList<String>(strings); 

       for(String s : stringsCme) { 
        if(s == null || !s.equals("abc")) { 
         //System.out.println("Wrong entry: " + s); 
         wrongEntries++; 
        } 
       } 

       if(i % 100 == 0) 
        System.out.println("i = " + i + "\t list: " + stringsCme.size() + ", #wrong entries: " + wrongEntries); 
      } 

      System.out.println("#Wrong entries: " + wrongEntries); 
     } 
    }); 
    creatorThread.start(); 

    for(int i=0; i<m; i++) { 
     strings.remove(MathUtils.random(strings.size()-1)); 
    } 
} 
관련 문제