다음 코드로 인해 ConcurrentModificationException
또는 다른 부작용이 발생합니까?복사 생성자를 사용하는 동안 목록을 동시에 수정하십시오.
ArrayList<String> newList = new ArrayList<String>(list);
위의 코드가 실행될 때 목록의 크기가 매우 커서 다른 스레드가 동시에 목록을 수정한다는 것을 고려하십시오.
다음 코드로 인해 ConcurrentModificationException
또는 다른 부작용이 발생합니까?복사 생성자를 사용하는 동안 목록을 동시에 수정하십시오.
ArrayList<String> newList = new ArrayList<String>(list);
위의 코드가 실행될 때 목록의 크기가 매우 커서 다른 스레드가 동시에 목록을 수정한다는 것을 고려하십시오.
편집 :
내 초기 응답이 '예'만 @JohnVint 올바르게 지적으로, 그것은 ArrayList
이 System.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);
당신은 당신이 여기에서 무엇을하고 있는지에 대해 생각해야합니다. 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는이를 선호합니다.
나는 @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));
}
}
'는'동기화되지 않는 한 Collections.synchronizedList'는 반복과 (목록)가 CME를 얻을 수 있습니다 { }' –
Yikes, @Peter, 감사합니다. 나는 대답을 고쳐 줄 것이다. – Gray
목록 크기가 1000000 인 CME를 실제로 구현할 수 없었습니다. 백업 배열이 복사 배열 의 System.arraycopy를 통해 복사되는 특별한 경우에는 현재 배열의 스냅 샷을 가져 옵니까? 목록 크기 조정에 영향을 줍니까? – Sushant