JAVA fail-fast 메커니즘
예 를 들 어,ArrayList:
교체 기의 빠 른 실패 행 위 는 보장 되 지 않 습 니 다.일반적으로 동기 화 되 지 않 고 수정 되 는 지 에 대해 어떠한 딱딱 한 보증 도 할 수 없 기 때 문 입 니 다.빠 른 실패 교체 기 는 Concurrent ModificationException 을 최대한 던 질 것 입 니 다.따라서 이러한 교체 기의 정확성 을 높이 기 위해 이 이상 에 의존 하 는 프로그램 을 만 드 는 것 은 잘못된 방법 입 니 다.교체 기의 빠 른 실패 행 위 는 bug 를 검사 하 는 데 만 사용 되 어야 합 니 다.
HashMap 중:
교체 기의 빠 른 실패 행 위 는 보장 되 지 않 습 니 다.일반적으로 동기 화 되 지 않 은 병행 수정 이 존재 할 때 어떠한 단호 한 보증 도 할 수 없습니다.빠 른 실패 교체 기 는 Concurrent ModificationException 을 던 지기 위해 최선 을 다 합 니 다.따라서 이 이상 한 프로그램 에 의존 하 는 방법 은 잘못 되 었 습 니 다.정확 한 방법 은 교체 기의 빠 른 실패 행 위 는 프로그램 오 류 를 검사 하 는 데 만 사용 되 어야 합 니 다.
이 두 단락 의 말 에서'빠 른 실패'를 반복 적 으로 언급 하 다.그렇다면'빠 른 실패'메커니즘 은 무엇 일 까?
'빠 른 실패'즉 fail-fast 입 니 다.자바 집합 에 대한 오류 검출 메커니즘 입 니 다.여러 스 레 드 가 집합 을 구조 적 으로 바 꾸 는 작업 을 할 때 fail-fast 메커니즘 이 생 길 수 있 습 니 다.기억 하 는 것 은 가능 한 것 이지,꼭 그렇지 는 않다.예 를 들 어 두 개의 스 레 드(스 레 드 1,스 레 드 2)가 존재 한다 고 가정 하고 스 레 드 1 은 Iterator 를 통 해 집합 A 의 요 소 를 옮 겨 다 니 며 어느 때 스 레 드 2 는 집합 A 의 구 조 를 수정 했다.(구조 상의 수정 이지 집합 요소 의 내용 을 간단하게 수정 하 는 것 이 아니 라 구조 상의 수정 이다)이 럴 때 프로그램 은 Concurrent ModificationException 이상 을 던 져 fail-fast 체 제 를 형성한다.
1.fail-fast 예제
public class FailFastTest {
private static List<Integer> list = new ArrayList<>();
/**
* @desc: one list
* @Project:test
* @file:FailFastTest.java
* @Authro:chenssy
* @data:2014 7 26
*/
private static class threadOne extends Thread{
public void run() {
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
int i = iterator.next();
System.out.println("ThreadOne :" + i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* @desc: i == 3 , list
* @Project:test
* @file:FailFastTest.java
* @Authro:chenssy
* @data:2014 7 26
*/
private static class threadTwo extends Thread{
public void run(){
int i = 0 ;
while(i < 6){
System.out.println("ThreadTwo run:" + i);
if(i == 3){
list.remove(i);
}
i++;
}
}
}
public static void main(String[] args) {
for(int i = 0 ; i < 10;i++){
list.add(i);
}
new threadOne().start();
new threadTwo().start();
}
}
실행 결과:
ThreadOne :0
ThreadTwo run:0
ThreadTwo run:1
ThreadTwo run:2
ThreadTwo run:3
ThreadTwo run:4
ThreadTwo run:5
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at test.ArrayListTest$threadOne.run(ArrayListTest.java:23)
2.fail-fast 발생 원인
위의 예제 와 설명 을 통 해 저 는 fail-fast 가 발생 하 는 원인 은 프로그램 이 collection 을 교체 할 때 특정한 스 레 드 가 이 collection 을 구조 적 으로 수정 한 것 을 알 게 되 었 습 니 다.이때 교체 기 는 Concurrent ModificationException 이상 정 보 를 던 져 fail-fast 가 발생 합 니 다.
fail-fast 메커니즘 을 이해 하려 면 먼저 Concurrent ModificationException 에 대해 알 아야 합 니 다.방법 이 대상 의 병발 수정 을 감지 하 였 으 나,이러한 수정 을 허용 하지 않 을 때 이 이상 을 던 집 니 다.또한 주의해 야 할 것 은 이 이상 은 대상 이 서로 다른 스 레 드 에서 동시에 수정 되 었 다 는 것 을 지적 하지 않 고 단일 스 레 드 가 규칙 을 위반 하면 이상 을 던 질 수도 있다 는 점 이다.
물론,교체 기의 빠 른 실패 행 위 는 보장 되 지 않 습 니 다.반드시 이 오류 가 발생 할 것 이 라 고 장담 할 수 없습니다.그러나 빠 른 실패 작업 은 Concurrent ModificationException 이상 을 던 지 려 고 최선 을 다 할 것 입 니 다.따라서 이러한 작업 의 정확성 을 높이 기 위해 이 이상 에 의존 하 는 프로그램 을 만 드 는 것 은 잘못된 방법 입 니 다.정확 한 방법 은:Concurrent ModificationException 은 bug 를 검사 하 는 데 만 사용 해 야 합 니 다.다음은 Array List 를 예 로 들 어 fail-fast 가 발생 하 는 원인 을 분석 하 겠 습 니 다.
앞에서 우 리 는 fail-fast 가 교체 기 를 조작 할 때 발생 한 다 는 것 을 알 았 다.이제 Array List 의 교체 기 소스 코드 를 살 펴 보 겠 습 니 다.
private class Itr implements Iterator<E> {
int cursor;
int lastRet = -1;
int expectedModCount = ArrayList.this.modCount;
public boolean hasNext() {
return (this.cursor != ArrayList.this.size);
}
public E next() {
checkForComodification();
/** */
}
public void remove() {
if (this.lastRet < 0)
throw new IllegalStateException();
checkForComodification();
/** */
}
final void checkForComodification() {
if (ArrayList.this.modCount == this.expectedModCount)
return;
throw new ConcurrentModificationException();
}
}
위의 소스 코드 를 통 해 알 수 있 듯 이 교체 기 는 next(),reove()방법 을 호출 할 때 모두 checkForComodification()방법 을 호출 합 니 다.이 방법 은 주로 modCount==expected ModCount 를 검사 하 는 것 입 니까?다 르 지 않 으 면 Concurrent ModificationException 이상 을 던 져 fail-fast 메커니즘 이 생 긴 다.그래서 왜 fail-fast 메커니즘 이 생 겼 는 지 알 아 내 려 면 왜 modCount 가 생 겼 는 지 알 아야 합 니 다!=expected ModCount,그들의 값 이 언제 바 뀌 었 습 니까?
expected ModCount 는 Itr 에서 정의 합 니 다:int expected ModCount=Array List.this.modCount;그래서 그의 값 은 수정 할 수 없 기 때문에 변 할 수 있 는 것 은 modCount 이다.modCount 는 AbstractList 에서 정 의 된 전역 변수 입 니 다.
protected transient int modCount = 0;
그렇다면 그 는 언제 어떤 이유 로 변 했 을 까?Array List 의 원본 코드 를 보십시오:
public boolean add(E paramE) {
ensureCapacityInternal(this.size + 1);
/** */
}
private void ensureCapacityInternal(int paramInt) {
if (this.elementData == EMPTY_ELEMENTDATA)
paramInt = Math.max(10, paramInt);
ensureExplicitCapacity(paramInt);
}
private void ensureExplicitCapacity(int paramInt) {
this.modCount += 1; // modCount
/** */
}
public boolean remove(Object paramObject) {
int i;
if (paramObject == null)
for (i = 0; i < this.size; ++i) {
if (this.elementData[i] != null)
continue;
fastRemove(i);
return true;
}
else
for (i = 0; i < this.size; ++i) {
if (!(paramObject.equals(this.elementData[i])))
continue;
fastRemove(i);
return true;
}
return false;
}
private void fastRemove(int paramInt) {
this.modCount += 1; // modCount
/** */
}
public void clear() {
this.modCount += 1; // modCount
/** */
}
위의 소스 코드 를 통 해 알 수 있 듯 이 Array List 에서 add,remove,clear 방법 은 Array List 요 소 를 바 꾸 는 개수 와 관련 된 방법 이 라면 modCount 의 변 화 를 초래 할 수 있 습 니 다.그래서 우 리 는 expected ModCount 의 수치 와 modCount 의 변화 가 일치 하지 않 기 때문에 둘 사이 가 다 르 기 때문에 fail-fast 메커니즘 이 생 긴 다 고 초보 적 으로 판단 할 수 있다.fail-fast 가 발생 하 는 근본 적 인 원인 을 알 게 되 었 습 니 다.우 리 는 다음 과 같은 장면 을 가 질 수 있 습 니 다.
두 개의 스 레 드(스 레 드 A,스 레 드 B)가 있 는데 그 중에서 스 레 드 A 는 list,스 레 드 B 를 옮 겨 다 니 며 list 를 수정 합 니 다.스 레 드 A 는 list 과정 을 옮 겨 다 닐 때(이때 expected ModCount=modCount=N)스 레 드 가 시작 되 고 스 레 드 B 에 요 소 를 추가 합 니 다.이것 은 modCount 의 값 이 바 뀌 었 습 니 다(modCount+1=N+1).스 레 드 A 가 다음 방법 을 계속 옮 겨 다 닐 때 checkForComodification 방법 에서 expected ModCount 를 발견 합 니 다. = N ,한편,modCount=N+1 은 둘 이 다 르 기 때문에 이때 Concurrent ModificationException 이상 을 던 져 fail-fast 메커니즘 이 생 긴 다.
그래서 여기까지 우 리 는 fail-fast 가 발생 하 는 근본 적 인 원인 을 완전히 알 게 되 었 다.이 유 를 알 면 해결책 을 찾기 쉽다.
3.fail-fast 해결 방법
앞의 사례,소스 코드 분석 을 통 해 저 는 여러분 들 이 fail-fast 의 체 제 를 기본적으로 이해 하 셨 다 고 생각 합 니 다.다음은 제 가 발생 한 원인 에 대해 해결 방안 을 제시 하 겠 습 니 다.여기에 두 가지 해결 방안 이 있다.
방안 1:옮 겨 다 니 는 과정 에서 modCount 를 바 꾸 는 것 과 관련 된 모든 부분 에 synchronized 를 추가 하거나 Collections.synchronized List 를 직접 사용 하면 해결 할 수 있 습 니 다.하지만 추천 하지 않 습 니 다.삭제 로 인 한 동기 화 잠 금 은 옮 겨 다 니 는 작업 을 막 을 수 있 습 니 다.
프로젝트 2:CopyOnWriteArrayList 를 사용 하여 ArrayList 를 교체 합 니 다.이 방안 을 추천 합 니 다.
CopyonWriteArray List 는 무엇 입 니까?Array List 의 안전 한 스 레 드 변 체 는 모든 가 변 조작(add,set 등)이 바 텀 배열 에 대한 새로운 복 제 를 통 해 이 루어 집 니 다.이런 종류의 비용 은 비교적 크 지만,두 가지 상황 에서 그것 은 매우 적합 하 다.1:동기 화 되 지 않 거나 옮 겨 다 니 고 싶 지 않 지만 동시 다발 스 레 드 에서 충돌 을 제거 해 야 할 때.2:옮 겨 다 니 는 작업 의 수량 이 가 변 작업 의 수량 을 크게 초과 할 때.이 두 가지 상황 이 발생 하면 CopyOnWriteArrayList 를 사용 하여 ArrayList 를 대체 하 는 것 이 가장 적합 하 다.그렇다면 왜 CopyOnWriterArrayList 가 ArrayList 를 대체 할 수 있 습 니까?
첫째,CopyOn WriterArrayList 는 데이터 구조,정의 에서 든 ArrayList 와 같 습 니 다.이 는 Array List 와 마찬가지 로 List 인 터 페 이 스 를 실현 하고 바 텀 은 배열 로 이 루어 집 니 다.방법 에 도 add,remove,clear,iterator 등 방법 이 포함 되 어 있다.
둘째,CopyOn WriterArrayList 는 Concurrent ModificationException 이상 이 발생 하지 않 습 니 다.즉,교체 기 를 사용 하면 fail-fast 메커니즘 이 전혀 발생 하지 않 습 니 다.보 세 요:
private static class COWIterator<E> implements ListIterator<E> {
/** */
public E next() {
if (!(hasNext()))
throw new NoSuchElementException();
return this.snapshot[(this.cursor++)];
}
/** */
}
CopyonWriterArrayList 의 방법 은 ArrayList 에서 checkForComodification 방법 을 사용 하여 expected ModCount 와 modCount 가 같은 지 판단 하 는 것 과 같 지 않다.그것 은 왜 이렇게 할 수 있 습 니까?무엇 때문에 이렇게 할 수 있 습 니까?우 리 는 add 방법 을 예 로 들 면:
public boolean add(E paramE) {
ReentrantLock localReentrantLock = this.lock;
localReentrantLock.lock();
try {
Object[] arrayOfObject1 = getArray();
int i = arrayOfObject1.length;
Object[] arrayOfObject2 = Arrays.copyOf(arrayOfObject1, i + 1);
arrayOfObject2[i] = paramE;
setArray(arrayOfObject2);
int j = 1;
return j;
} finally {
localReentrantLock.unlock();
}
}
final void setArray(Object[] paramArrayOfObject) {
this.array = paramArrayOfObject;
}
CopyOnWriterArrayList 의 add 방법 과 ArrayList 의 add 방법 은 가장 큰 차이 점 이 있 는데 바로 다음 세 개의 코드 입 니 다.
Object[] arrayOfObject2 = Arrays.copyOf(arrayOfObject1, i + 1);
arrayOfObject2[i] = paramE;
setArray(arrayOfObject2);
바로 이 세 개의 코드 로 인해 CopyOnWriterArrayList 는 Concurrent ModificationException 이상 을 버 리 지 않 습 니 다.그들 이 보 여 주 는 매력 은 바로 copy 의 원래 array 에 있다.다시 copy 배열 에서 add 작업 을 하면 COWIterator 의 array 에 전혀 영향 을 주지 않 는 다.
그래서 CopyOnWriterArrayList 가 대표 하 는 핵심 개념 은 array 가 구조 적 으로 변 하 는 모든 조작(add,remove,clear 등),CopyOnWriterArrayList 는 기 존의 데 이 터 를 복사 하고 copy 의 데이터 에 수정 하면 COWIterator 의 데이터 에 영향 을 주지 않 습 니 다.수정 이 끝 난 후에 기 존의 데이터 의 인용 을 바 꾸 면 됩 니 다.이와 함께 이 로 인 한 대 가 는 대량의 대상 이 발생 하 는 동시에 배열 의 copy 도 상당히 손실 된다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.