동시 목록 소스 코드 분석

동시 패키지의 유일한 동시 목록은 CopyOnWriteArrayList입니다.

CopyOnWriteArrayList는 스레드로부터 안전한 ArrayList이며 수정 작업은 기본 레이어의 복사된 배열(스냅샷)에서 수행됩니다. 즉, 쓰기 중 복사 전략이 사용됩니다.

여기에 이미지 설명을 삽입하세요.

CopyOnWriteArrayList의 클래스 다이어그램에서 각 CopyOnWriteArrayList 개체에는 특정 요소를 저장하는 배열 배열 개체가 있으며, ReentrantLock 독점 잠금 개체는 동시에 하나의 스레드만 배열을 수정하도록 보장하는 데 사용됩니다.

여기서 ReentrantLock은 배타적 잠금이고 동시에 하나의 스레드만 획득할 수 있다는 점을 기억하세요. JUC의 잠금은 나중에 소개됩니다.

쓰기 중 복사 스레드 안전 목록을 작성하라는 요청을 받은 경우 어떻게 해야 하며 어떤 점을 고려해야 합니까?

  • 목록은 언제 초기화됩니까? 초기화된 목록 요소의 수는 얼마입니까? 목록의 크기가 제한되어 있습니까?
  • 스레드 안전성을 보장하는 방법(예: 여러 스레드가 읽고 쓸 때 스레드 안전성을 보장하는 방법)은 무엇입니까?
  • 반복자를 사용하여 목록을 탐색할 때 데이터 일관성을 보장하는 방법은 무엇입니까?

주요 메소드 소스코드 분석

초기화

먼저 매개변수가 없는 생성자를 살펴보면, 다음 코드는 배열의 초기값으로 크기가 0인 Object 배열을 내부적으로 생성합니다.
여기에 이미지 설명을 삽입하세요.
그런 다음 매개변수화된 생성자를 살펴보세요.

여기에 이미지 설명을 삽입하세요.

요소 추가

CopyOnWriteArrayList에 요소를 추가하는 데 사용되는 함수에는 add(E e), add(int index,E element), addIfAbsent(E e) 및 addAllAbsent(Collection<?extends E> c)가 있으며, 원리는 유사하므로 add(E e) 설명을 위해 예를 들어보겠습니다.

여기에 이미지 설명을 삽입하세요.

위의 코드에서 add 메소드를 호출한 스레드는 먼저 코드 (1)을 실행하여 배타적 잠금을 획득합니다. 여러 스레드가 add 메소드를 호출하면 단 하나의 스레드만 잠금을 획득하고 다른 스레드는 까지 차단되고 일시 중지됩니다. 잠금이 해제되었습니다.

따라서 스레드가 잠금을 획득한 후에는 스레드가 요소를 추가하는 동안 다른 스레드가 배열을 수정하지 않는다는 것이 보장됩니다.

스레드가 잠금을 획득한 후 코드 (2)를 실행하여 배열을 얻은 다음 코드 (3)을 실행하여 배열을 새 배열에 복사합니다. (여기서 새 배열의 크기가 원래 배열임을 알 수 있습니다.) 크기가 1만큼 증가하므로 CopyOnWriteArrayList는 제한되지 않은 목록입니다.) 새 배열은 새 배열에 추가 요소를 추가합니다.

그런 다음 코드 (4)를 실행하여 원래 배열을 새 배열로 바꾸고 반환하기 전에 잠금을 해제합니다.

잠금으로 인해 전체 추가 프로세스는 원자성 작업입니다.

요소를 추가할 때 스냅샷이 먼저 복사된 다음 원래 배열에 직접 추가되지 않고 스냅샷에 추가된다는 점에 유의해야 합니다.

지정된 위치의 요소를 가져옵니다.

E get(int index)를 사용하여 아래 첨자가 index인 요소를 가져옵니다. 해당 요소가 존재하지 않으면 IndexOutOfBoundsException 예외가 발생합니다.
여기에 이미지 설명을 삽입하세요.
위 코드에서 thread )의 경우 2단계 작업이지만 전체 프로세스 동안 잠금 동기화가 수행되지 않습니다.

이때 List의 내용은 그림과 같으며 그 안에 1, 2, 3의 세 가지 요소가 들어 있다고 가정합니다.

여기에 이미지 설명을 삽입하세요.

A 단계와 B 단계를 실행할 때 잠금이 없으므로 이로 인해 스레드 x가 A 단계 실행을 마친 후 B 단계를 실행하기 전에 다른 스레드 y가 제거 작업을 수행할 수 있습니다. 요소 1이 삭제된다고 가정합니다.

제거 작업은 먼저 배타적 잠금을 획득한 다음 쓰기 중 복사 작업, 즉 현재 배열의 복사본을 복사한 다음 스레드 x가 복사본에서 get 메서드를 통해 액세스하려는 요소 1을 삭제합니다. 그런 다음 배열이 복사된 배열을 가리키도록 합니다.

이때, array가 가리키는 배열의 참조 횟수는 스레드 x가 아직 사용 중이기 때문에 0이 아닌 1이고, 이때 스레드 x는 B 단계를 실행하기 시작합니다. 스레드 y는 요소를 삭제합니다.

여기에 이미지 설명을 삽입하세요.
따라서 스레드 y가 인덱스의 요소를 삭제하더라도 스레드 x의 단계 B는 여전히 인덱스의 요소를 반환합니다. 이는 실제로 쓰기 시 복사 전략으로 인해 발생하는 약한 일관성 문제입니다.

지정된 요소 수정

E set(int index, E element)를 사용하여 목록에서 지정된 요소의 값을 수정합니다. 지정된 위치의 요소가 존재하지 않으면 IndexOutOfBoundsException 예외가 발생합니다.

여기에 이미지 설명을 삽입하세요.
위 코드는 다른 스레드가 배열을 수정하지 못하도록 먼저 배타적 잠금을 획득한 후 현재 배열을 획득하고 get 메소드를 호출하여 지정된 위치의 요소 값을 획득합니다. 값을 입력하면 새 배열이 생성되고 요소가 복사됩니다. 그런 다음 새 배열의 지정된 위치에서 요소 값을 수정하고 새 배열을 배열로 설정합니다.

지정된 위치에 있는 요소의 값이 새 값과 동일한 경우 휘발성 의미를 보장하기 위해 배열의 내용은 변경되지 않았더라도 배열을 재설정해야 합니다.

요소 삭제

목록에서 지정된 요소를 삭제하려면 Eremove(int index), boolean Remove(Object o), boolean Remove(Object o, Object[] snapshot, int index) 등의 메소드를 사용할 수 있으며 그 원리는 동일합니다. . Remove(int index) 메소드는 아래에 설명되어 있습니다.

여기에 이미지 설명을 삽입하세요.
위 코드는 실제로 새로운 요소를 추가하는 코드와 유사하며, 데이터 삭제 중에 다른 스레드가 배열을 수정할 수 없도록 먼저 배타적 잠금을 획득한 다음, 배열에서 삭제할 요소를 획득하고 나머지를 복사합니다. 요소를 새 배열에 추가한 다음 새 배열이 원래 배열을 대체하고 마지막으로 반환되기 전에 잠금이 해제됩니다.

약하게 일관된 반복자

반복자를 사용하여 목록 요소를 순회할 수 있습니다. 반복자의 약한 일관성이 무엇인지 설명하기 전에 반복자를 사용하는 방법을 설명하는 예를 들어 보겠습니다.

여기에 이미지 설명을 삽입하세요.
iterator의 hasNext 메소드는 목록에 아직 요소가 있는지 확인하는 데 사용되며, next 메소드는 해당 요소를 구체적으로 반환합니다. CopyOnWriteArrayList에서 반복자의 약한 일관성을 살펴보겠습니다. 소위 약한 일관성이란 반복자가 반환된 후 다른 스레드에 의한 목록의 추가, 삭제 및 변경 사항이 반복자에게 표시되지 않음을 의미합니다. 이것 좀 보세요 어떻게 해야 할까요?

여기에 이미지 설명을 삽입하세요.
반복자를 얻기 위해 iterator() 메서드를 호출하면 실제로는 COWIterator 객체가 반환되는데, COWIterator 객체의 snapshot 변수는 현재 목록의 내용을 저장하고, 목록을 순회할 때 커서는 데이터의 첨자입니다.

스냅샷이 리스트의 스냅샷이라고 하는 이유는 분명 복사본이 아닌 포인터에 의해 전달된 참조입니다.

스레드가 반환된 반복자를 사용하여 요소를 순회하는 동안 다른 스레드가 목록을 추가, 삭제 또는 수정하지 않으면 스냅샷 자체는 참조 관계이기 때문에 목록의 배열입니다.

그러나 순회 중에 다른 스레드가 목록을 추가, 삭제 또는 수정하는 경우 추가, 삭제 또는 수정 후 목록의 배열이 새 배열로 대체되고 이전 배열이 참조되기 때문에 스냅샷은 스냅샷입니다. 이때 스냅샷으로

이는 또한 반복자를 얻은 후 반복자 요소를 사용할 때 다른 스레드가 목록에 추가, 삭제 및 수정한 내용이 두 개의 서로 다른 배열을 작동하기 때문에 표시되지 않음을 보여줍니다. 이는 약한 일관성입니다.

다음은 멀티스레딩에서 반복자의 약한 일관성이 미치는 영향을 보여주는 예입니다.
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.

위 코드에서 기본 함수는 먼저 arrayList를 초기화한 다음 스레드를 시작하기 전에 arrayList 반복자를 가져옵니다.

하위 스레드 threadOne이 시작된 후 먼저 arrayList의 첫 번째 요소 값을 수정한 다음 arrayList에서 아래 첨자 2와 3이 있는 요소를 삭제합니다.

하위 스레드의 실행이 완료된 후 메인 스레드는 획득한 반복자를 사용하여 배열 요소를 순회합니다. 출력 결과를 통해 하위 스레드에서 수행된 작업 중 아무 것도 적용되지 않았음을 알 수 있습니다. 이는 약한 일관성의 표현입니다. 반복자의.

반복자를 얻는 작업은 하위 스레드 작업보다 먼저 수행되어야 한다는 점에 유의해야 합니다.

추천

출처blog.csdn.net/zhuyufan1986/article/details/135456564