1. FAIL-FAST 소개
"빠른 실패"는 FAIL-FAST라고도하며, 이는 Java 컬렉션의 오류 감지 메커니즘입니다. 스레드가 컬렉션을 반복하면 다른 스레드는 컬렉션을 구조적으로 수정할 수 없습니다.
예를 들어 : 두 개의 스레드 (스레드 1 및 스레드 2)가 있다고 가정하고 스레드 1은 세트 A에서 반복자를 통과합니다. 어느 시점에서 스레드 2는 세트 A의 구조를 수정하여 (세트 요소의 내용을 단순히 수정하는 대신 구조에 대한 수정)를 수정하면 프로그램이 동시 모듈화 외 예외를 던져 실패를 생성합니다.
반복자의 빠른 고장 동작을 보장 할 수 없으며 오류가 발생한다고 보장 할 수 없으므로 ConcurrentModificationException은 버그를 감지하는 데만 사용해야합니다.
java.util 패키지의 모든 컬렉션 클래스는 빠르게 실패하는 반면 java.util.concurrent 패키지의 컬렉션 클래스는 안전하게 실패합니다.
실패한 반복자는 동시 모형화 소집을 빠르게 던지고, 실패한 반복기는 안전하게이 예외를 던지지 않습니다.
2 번의 빠른 예제
샘플 코드 : (FastFailTest.java)
java.util.*; import java.util.concurrent.*;/** @DESC 테스트 프로그램을위한 빠른 결점 컬렉션. * * 빠른 실패 이벤트 조건 : 컬렉션에서 여러 스레드가 작동하는 경우 스레드 중 하나가 반복자를 통해 컬렉션을 가로 지르면 컬렉션의 내용이 다른 스레드에 의해 변경됩니다. 동시 변형 추출 예외가 발생합니다. * 빠른 풀 솔루션 : UTIL.Concurrent Collection 패키지에서 해당 클래스를 통해 처리하면 빠른 실패 이벤트가 생성되지 않습니다. * *이 예에서는 ArrayList 및 LoveONWRITEARRAYLIST의 두 가지 사례가 각각 테스트됩니다. Arraylist는 빠른짜리 이벤트를 생성하는 반면, LoveonWriteArraylist는 빠른짜리 이벤트를 생성하지 않습니다. * (01) ArrayList를 사용하면 빠른 실패 이벤트가 생성되고 동시 모듈 식 소식 예외가 발생합니다. 정의는 다음과 같습니다. * 개인 정적 목록 <string> list = new Arraylist <string> (); * (02) 복사상 WriteArrayList를 사용할 때 빠른 실패 이벤트가 생성되지 않습니다. 정의는 다음과 같습니다. * 개인 정적 목록 <string> list = new CopyonWriteArrayList <string> (); * * @Author Skywang */public class fastfailtest {private static list <string> list = new arraylist <string> (); // 비공개 정적 목록 <string> list = new CopyOnwriteArrayList <string> (); public static void main (String [] args) {// 목록에서 작동하기 위해 동시에 두 개의 스레드를 시작합니다! new ThreadOn (). start (); 새 threadtwo (). start (); } private static void printall () {System.out.println ( ""); 문자열 값 = null; 반복자 iter = list.iterator (); while (iter.hasnext ()) {value = (string) iter.next (); System.out.print (value+","); }} /*** 목록에 0,1,2,3,4,5를 추가하십시오. 숫자를 추가 한 후 printall () */ private static class 스레드 톤을 통해 반복 스레드 {public void run () {int i = 0; while (i <6) {list.add (string.valueof (i)); printall (); i ++; }}} /*** 목록에 10,11,12,13,14,15를 추가하십시오. 각 숫자가 추가되면 printall () */ private static class Threadtwo를 통해 전체 목록을 가로 지르며 스레드 {public void run () {int i = 10; while (i <16) {list.add (string.valueof (i)); printall (); i ++; }}}} 결과적으로 코드를 실행하고 예외 Java.util.concurrentModificationException을 던졌습니다! 즉, 빠른 이벤트가 생성됩니다!
결과 설명
(01) FastFailTest에서는 New ThreadOn (). start () 및 new ThreadTwo (). start ()를 통해 목록을 조작하기 위해 동시에 두 개의 스레드를 시작합니다.
스레드 스레드 : 목록에 0, 1, 2, 3, 4, 5를 추가하십시오. 각 숫자가 추가되면 전체 목록은 printall ()을 통해 트래버됩니다.
ThreadTwo 스레드 : 목록에 10, 11, 12, 13, 14, 15를 추가하십시오. 각 숫자가 추가되면 전체 목록은 printall ()을 통해 트래버됩니다.
(02) 스레드가 목록을 가로 지르면 목록의 내용이 다른 스레드에 의해 변경됩니다. 동시 변형 외과 예외가 발생하여 불쾌한 이벤트가 발생합니다.
3. FAIN-FAST 솔루션
실패 메커니즘은 오류 감지 메커니즘입니다. JDK가 실패한 메커니즘이 발생할 것이라고 보장하지 않기 때문에 오류를 감지하는 데만 사용될 수 있습니다. 다중 스레드 환경에서 실패한 메커니즘 모음을 사용하는 경우 "java.util.concurrent 패키지의 클래스를 사용하여"java.util 패키지의 클래스 "를 대체하는 것이 좋습니다.
따라서이 예에서는 arraylist를 java.util.concurrent 패키지의 해당 클래스로만 바꾸면됩니다. 즉, 코드입니다
개인 정적 목록 <string> list = new ArrayList <string> ();
대체하십시오
비공개 정적 목록 <string> list = new CopyonWriteArrayList <string> ();
이 솔루션은 해결할 수 있습니다.
4. 빠른 원칙
FAIL-FAST 이벤트가 생성되며 동시 계수 예외 예외를 던지면 트리거됩니다.
그렇다면 ArrayList는 어떻게 동시 모듈화 외 예외를 던지는가?
우리는 ConcurrentModificationException이 반복자를 작동시킬 때 발생하는 예외라는 것을 알고 있습니다. 먼저 반복자 소스 코드를 살펴 보겠습니다. ArrayList의 반복자는 Parent Class AbstractList.java에서 구현됩니다. 코드는 다음과 같습니다.
패키지 java.util;
public acc // 목록을 위해 반복자를 반환합니다. 실제로, 그것은 ITR 객체를 반환하는 것입니다. public iterator <e> iterator () {return new itr (); } // ITR은 ITERATOR (ITERATOR)의 구현 클래스입니다. 개인 클래스 ITR은 반복자 <e> {int cursor = 0; int lastret = -1; // 숫자의 레코드 값을 수정합니다. // 새 ITR () 객체가 생성 될 때마다 새 개체가 생성 될 때 해당 모드가 저장됩니다. // 목록의 요소를 가로 질러 횡단 할 때마다 예상 MODCOUNT 및 MODCOUNT가 동일한지 비교할 수 있습니다. // 동일하지 않으면 동시 계수 예상 예외가 발생하여 빠른 이벤트가 발생합니다. int exclingModCount = modCount; public boolean hasnext () {return cursor! = size (); } public e next () {// 다음 요소를 얻기 전에 "새 ITR 객체를 생성 할 때 저장된 ModCount"및 "current modcount"가 동일한지 여부가 판단됩니다. // 동일하지 않으면 동시 변형 외과 예외가 발생하여 빠른 이벤트가 발생합니다. CheckforComodification (); {e next = get (cursor); lastret = 커서 ++; 다음으로 돌아갑니다. } catch (indexOutOfBoundSexection e) {checkforcomodification (); 새로운 nosuchelementexception ()을 던지십시오. }} public void remove () {if (lastret == -1) 새로운 불법 스테이트 렉스크 () 던지기; CheckforComodification (); try {acpractList.this.remove (lastret); if (lastret <cursor) 커서-; lastret = -1; 예상 modCount = modCount; } catch (indexOutOfBoundSexception e) {throw new concurrentModificationException (); }} Final void checkforcomodification () {if (modCount! = excliteModCount) 새 concurrentModificationException (); }} ...} 이것으로부터, 우리는 checkforcomodification ()이 다음 ()와 remove ()가 호출 될 때 실행되는 것을 발견 할 수 있습니다. "modcount가 expectModCount와 같지 않은 경우"동시 모성 외과 예외가 발생하여 빠른 이벤트가 발생합니다.
실패한 메커니즘을 이해하려면 "ModCount가 예상 모드 카운트와 같지 않을 때"를 이해해야합니다!
ITR 클래스에서 우리는 ITR 객체를 만들 때 exportModCount가 modCount에 할당된다는 것을 알고 있습니다. ITR을 통해 우리는 예상 modcount가 동등하지 않도록 수정 될 수 없다는 것을 알고 있습니다. 따라서 확인해야 할 것은 ModCount가 수정 될 때입니다.
다음으로, 배열리스트의 소스 코드를 확인하여 modcount가 어떻게 수정되었는지 확인해 봅시다.
package java.util; public class arraylist <e>는 acpractList를 확장합니다. actractList <e>는 목록 <e>, randomAccess, clonable, java.io.serializable {... // 목록의 용량이 변경되면 공개 void ensurecapacity (int mincapacity) {modcount ++; int OldCapacity = ElementData.length; if (minCapacity> OldCapacity) {Object OldData [] = ElementData; int newCapacity = (OldCapacity * 3)/2 + 1; if (newCapacity <mincapacity) newCapacity = mincapacity; // mincapacity는 일반적으로 크기에 가깝기 때문에 이것은 다음과 같습니다. elementData = arrays.copyof (elementData, newCapacity); }} // 큐의 마지막 공개 부울 add (e e)에 요소 추가 {// modcount ensurecapacity (size + 1); // MODCOUNT를 증가시킵니다 !! ElementData [size ++] = e; 진실을 반환하십시오. } // 지정된 위치에 요소 추가 공개 void add (int index, e element) {if (index> size || index <0) 새 indexOutOfBoundSexception 던지기 ( "index :"+index+", size :"+size); // modcount ensurecapacity (size+1)를 수정합니다. // MODCOUNT를 증가시킵니다 !! System.ArrayCopy (ElementData, Index, ElementData, Index + 1, Size -Index); ElementData [index] = 요소; 크기 ++; } // 컬렉션 추가 공개 부울 addall (collection <? extends e> c) {object [] a = c.toArray (); int numnew = a.length; // modcount ensurecapacity (size + numnew)를 수정합니다. // MODCOUNT SYSTEM.ARRAYCOPY (A, 0, ElementData, Size, NumNew); 크기 += numnew; NumNew! = 0을 반환합니다. } // 지정된 위치에서 요소를 삭제 공개 e 제거 (int index) {rangecheck (index); // modcount modcount ++ 수정; e OldValue = (e) ElementData [index]; int nummoved = size -index -1; if (nummoved> 0) System.arrayCopy (ElementData, index+1, elementData, index, nummoved); ElementData [-size] = null; // GC가 작업을 수행하도록하자 OldValue를 반환합니다. } // 지정된 위치에서 요소를 빠르게 삭제하십시오 Private void fastremove (int index) {// modcount modcount ++ 수정; int nummoved = size -index -1; if (nummoved> 0) System.arrayCopy (ElementData, index+1, elementData, index, nummoved); ElementData [-size] = null; // gc가 작업을 수행하도록하자} // 컬렉션을 지우십시오 public void clear () {// modcount modcount ++; // gc가 작동하도록하십시오 (int i = 0; i <size; i ++) elementData [i] = null; 크기 = 0; } ...} 이것으로부터, 우리는 그것이 add (), remove () 또는 clear ()이든 세트의 요소 수를 수정하는 한 변경 될 것임을 발견했습니다.
다음으로, 실패-빠르게 생성되는 방법을 체계적으로 정리해 봅시다. 단계는 다음과 같습니다.
(01) ArrayList로 새 ArrayList 이름을 만듭니다.
(02) Arraylist에 컨텐츠를 추가하십시오.
(03) 새 "스레드 A"를 만들고 "스레드 A"의 반복자를 통해 ArrayList의 값을 반복적으로 읽으십시오.
(04) 새 "스레드 B"를 생성하고 "스레드 B"의 ArrayList에서 "노드 A"를 삭제하십시오.
(05) 현재로서는 흥미로운 사건이 발생합니다.
어느 시점에서 "Thread a"는 Arraylist의 반복자를 만듭니다. 현재 "노드 A"는 여전히 배열 목록에 존재합니다. ArrayList를 만들 때 예상 modCount = modCount (현재 값이 N이라고 가정).
Arraylist를 가로 지르는 과정에서 어느 시점에서 "Thread B"가 실행되고 Arraylist의 "Thread B"Node A "가 실행되고"스레드 B "가 A"를 실행합니다. "스레드 B"가 삭제 작업을 위해 remove ()를 실행하면 "modcount ++"가 remove ()에서 실행되고 modcount가 n+1이됩니다!
"실을 a"다음 통과합니다. 다음 () 함수를 실행하면 checkforcomodification ()가 "예상 modcount"및 "modcount"의 크기를 비교하도록 호출됩니다. "예상 modcount = n", "modcount = n+1"이므로 동시 모듈 식 소식 예외가 발생하여 실패 이벤트가 발생합니다.
이 시점에서, 우리는 실패-빠른 생성 방법을 완전히 이해하고 있습니다!
즉, 동일한 세트에서 여러 스레드가 작동 할 때 스레드가 세트에 액세스 할 때 세트의 내용이 다른 스레드에 의해 변경됩니다 (즉, 다른 스레드는 ADD, 제거, 지우기 및 기타 메소드를 통해 MODCOUNT의 값을 변경합니다). 이 시점에서 동시 계산 예외 예외가 발생하여 불쾌한 이벤트가 발생합니다.
5. 불쾌감을 해결하는 원리
위의 내용은 "실패한 메커니즘을 해결하는 방법"을 설명하고 "고장의 근본 원인"을 알고 있습니다. 다음으로 java.util.concurrent 패키지에서 실패 이벤트를 해결하는 방법에 대해 더 이야기 해 봅시다.
ArrayList에 해당하는 COPYONWRITEARRAYLIST로 설명해 봅시다. 먼저 복사상 WriteArraylist의 소스 코드를 살펴 보겠습니다.
package java.util.concurrent; import java.util.*; import java.util.concurrent.locks. iterator () {새 컬렉션 클래스에서 빠른 구현 방법을 반환하는 것은 거의 동일합니다. 가장 간단한 배열 목록을 예로 들어 봅시다. 보호 된 과도 int modcount = 0; ArrayList를 수정하는 횟수를 기록합니다. 예를 들어, add (), remove () 등을 호출 할 때 데이터를 변경하려면 ModCount ++가 변경됩니다. 보호 된 과도 int modcount = 0; ArrayList를 수정하는 횟수를 기록합니다. 예를 들어, add (), remove () 등을 호출 할 때 데이터를 변경하려면 ModCount ++가 변경됩니다. Cowiterator <e> (GetArray (), 0); }. 개인 int 커서; Private Cowiterator (Object [] Elements, int initialCursor) {Cursor = InitialCursor; // 새 Cowiterator를 만들 때 컬렉션의 요소를 새 사본 배열로 저장하십시오. // 이러한 방식으로 원래 세트의 데이터가 변경되면 사본 데이터의 값도 변경되지 않습니다. 스냅 샷 = 요소; } public boolean hasnext () {return cursor <snapshot.length; } public boolean hasprevious () {return cursor> 0; } public e next () {if (! hasnext ()) 던지기 새로운 nosuchelementException (); 리턴 (e) 스냅 샷 [커서 ++]; } public e previous () {if (! haspRevious ()) 던지기 새로운 nosuchelementException (); 반환 (e) 스냅 샷 [-커서]; } public int nextIndex () {return cursor; } public int previousIndex () {return cursor-1; } public void remove () {새로운 UnsupportedOperationException (); } public void set (e e) {새로운 UnsupportedOperationException (); } public void add (e e) {새로운 UnsupportedOperationException (); }} ...} 이것으로부터 우리는 다음을 볼 수 있습니다.
(01) arraylist와 달리 AbstractList의 상속과 달리, CopyOnWriteArrayList는 AbstractList에서 상속되지 않으며 List Interface 만 구현합니다.
(02) iterator () 함수에 의해 반환 된 반복자는 AbstractList에서 구현됩니다. 복사본 WriteAreRaylist는 반복자 자체를 구현합니다.
(03) 다음 ()가 ArrayList의 Ierator 구현 클래스에서 호출되면, "expostModCount '및'modCount '의 크기를 비교하기 위해"checkforcomodification () "; 그러나 반복자 구현 클래스의 CopyonWriteArrayList에는 소위 CheckForComodification ()이 없으며 동의 모임 지연이 발생하지 않을 것입니다!
6. 요약
해시 맵 (arraylist)이 스레드-안전하지 않기 때문에 다른 스레드가 반복기를 사용하는 과정에서 다른 스레드가 맵을 수정하면 (여기서 수정은 단순히 수집 컨텐츠의 요소를 수정하는 것이 아니라 구조적 수정을 지칭한다) 동시 모형화를 방해하는 전략은 주로 실행 사이의 가상을 보장하기 위해 주로 구현 될 것이다. ModCount는 수정 수입니다. 이 값은 해시 맵 (Arraylist) 컨텐츠의 수정에 추가됩니다. 그런 다음 반복기의 초기화 중에이 값은 반복자의 예상 ModCount에 할당됩니다.
그러나 실패한 행동은 보장되지 않으므로이 예외에 의존하는 관행은 잘못입니다.