다음은이 작업을 수행하는 방법에 대한 설명과 예입니다. 명확하지 않은 부분이 있으면 알려주십시오.
소스와 요점
만능인
초기화 :
스레드 인덱스는 원자 단위로 적용됩니다. 이것은 AtomicInteger
named을 사용하여 관리됩니다 nextIndex
. 이 인덱스는 ThreadLocal
다음 인덱스를 가져 와서 nextIndex
증가 시켜 자체적으로 초기화 되는 인스턴스를 통해 스레드에 할당됩니다 . 이것은 각 스레드의 인덱스를 처음 검색 할 때 발생합니다. ThreadLocal
이 스레드가 생성 한 마지막 시퀀스를 추적하기 위해 A 가 생성됩니다. 0으로 초기화됩니다. 순차 팩토리 오브젝트 참조가 전달되어 저장됩니다. AtomicReferenceArray
size의 두 인스턴스가 작성 n
됩니다. 꼬리 객체는 각 참조에 할당되며 Sequential
공장에서 제공 한 초기 상태로 초기화됩니다 . n
허용되는 최대 스레드 수입니다. 이 배열의 각 요소는 해당 스레드 인덱스에 속합니다.
방법을 적용하십시오 :
이것이 흥미로운 작업을 수행하는 방법입니다. 다음을 수행합니다.
- 이 호출에 대한 새 노드를 작성하십시오. mine
- 현재 스레드의 인덱스에있는 Announce 배열에서이 새 노드를 설정하십시오.
그런 다음 시퀀싱 루프가 시작됩니다. 현재 호출이 순서화 될 때까지 계속됩니다.
- 이 스레드에 의해 작성된 마지막 노드의 순서를 사용하여 announce 배열에서 노드를 찾으십시오. 이것에 대해서는 나중에 더 설명하겠습니다.
- 2 단계에서 노드가 발견되면 아직 시퀀싱되지 않았으며 계속 진행하십시오. 그렇지 않으면 현재 호출에 집중하십시오. 이것은 호출 당 하나의 다른 노드 만 도와 주려고합니다.
- 3 단계에서 선택한 노드가 무엇이든 마지막 시퀀싱 된 노드 이후에 계속 시퀀싱하려고 시도합니다 (다른 스레드가 간섭 할 수 있음). 성공 여부에 관계없이 현재 스레드 헤드 참조가 반환 한 시퀀스를 설정합니다.
decideNext()
위에서 설명한 중첩 루프의 핵심은 decideNext()
방법입니다. 이를 이해하려면 Node 클래스를 살펴 봐야합니다.
노드 클래스
이 클래스는 이중 연결 목록에서 노드를 지정합니다. 이 수업에는 별다른 조치가 없습니다. 대부분의 방법은 설명이 간단한 간단한 검색 방법입니다.
꼬리 방법
이것은 순서가 0 인 특수 노드 인스턴스를 리턴합니다. 호출이이를 대체 할 때까지 플레이스 홀더 역할을합니다.
특성 및 초기화
seq
: 시퀀스 번호, -1로 초기화 됨 (시퀀싱되지 않음)
invocation
:의 호출 값 apply()
. 시공을 시작합니다.
next
: AtomicReference
정방향 링크 용. 일단 할당되면 변경되지 않습니다
previous
: AtomicReference
시퀀싱시 할당되고 다음에 의해 지워진 역방향 링크truncate()
다음 결정
이 방법은 사소한 논리를 가진 노드에서만 사용됩니다. 간단히 말해서, 노드는 링크 된 목록에서 다음 노드가 될 후보로 제공됩니다. 이 compareAndSet()
메소드는 참조가 널인지 확인하고, 참조 인 경우 참조를 후보로 설정합니다. 참조가 이미 설정되어 있으면 아무 것도 수행하지 않습니다. 이 작업은 원자 적이므로 두 명의 후보자가 동시에 제공되면 하나만 선택됩니다. 이렇게하면 하나의 노드 만 다음 노드로 선택됩니다. 후보 노드를 선택하면 순서가 다음 값으로 설정되고 이전 링크가이 노드로 설정됩니다.
범용 클래스 적용 메소드로 돌아 가기 ...
배열 decideNext()
에서 노드 또는 노드로 마지막 시퀀싱 된 노드 (확인 된 경우) 를 호출 한 경우 announce
두 가지 가능성이 있습니다. 1. 노드가 성공적으로 시퀀싱되었습니다. 2. 다른 스레드가이 스레드를 선점했습니다.
다음 단계는이 호출을 위해 노드가 작성되었는지 확인하는 것입니다. 이 스레드가 성공적으로 시퀀싱했거나 일부 다른 스레드가 announce
어레이 에서 스레드를 선택하여 시퀀싱했기 때문에 발생할 수 있습니다. 시퀀싱되지 않은 경우 프로세스가 반복됩니다. 그렇지 않으면이 스레드의 인덱스에서 announce 배열을 지우고 호출 결과 값을 반환하여 호출이 완료됩니다. Announce 배열은 노드가 가비지 수집되지 않도록하는 노드에 대한 참조가 없음을 보장하기 위해 지워 지므로 연결된 목록의 모든 노드가 힙의 활성 지점에서 유지됩니다.
방법 평가
호출 노드가 성공적으로 순서화되었으므로 호출을 평가해야합니다. 이를 수행하기위한 첫 번째 단계는이 단계 이전의 호출이 평가되었는지 확인하는 것입니다. 그들이하지 않은 경우이 스레드는 기다리지 않지만 즉시 작동합니다.
VerifyPrior 방법
이 ensurePrior()
방법은 연결된 목록에서 이전 노드를 확인하여이 작업을 수행합니다. 상태가 설정되지 않으면 이전 노드가 평가됩니다. 이것이 재귀적인 노드입니다. 이전 노드 이전의 노드가 평가되지 않은 경우 해당 노드에 대한 평가 등을 호출합니다.
이전 노드에 상태가있는 것으로 알려져 있으므로이 노드를 평가할 수 있습니다. 마지막 노드가 검색되어 로컬 변수에 할당됩니다. 이 참조가 null의 경우, 다른 thread가이 thread를 선취 해, 이미이 노드를 평가 한 것을 나타냅니다. 상태를 설정합니다. 그렇지 않으면 이전 노드의 상태 Sequential
가이 노드의 호출과 함께 객체의 apply 메소드로 전달 됩니다. 리턴 된 상태는 노드에서 설정되고 truncate()
메소드가 호출되어 더 이상 필요하지 않으므로 노드에서 역방향 링크를 지 웁니다.
MoveForward 방법
앞으로 이동 방법은 모든 헤드 참조가 아직 더 이상 무언가를 가리 키지 않으면이 노드로 이동하려고 시도합니다. 이것은 쓰레드가 호출을 멈 추면 더 이상 필요하지 않은 노드에 대한 참조를 유지하지 않도록하기위한 것입니다. 이 compareAndSet()
메소드는 다른 스레드가 검색된 후 노드를 변경하지 않은 경우에만 노드를 업데이트하도록합니다.
배열 및 도움 발표
단순히 잠금없는 방식과 달리이 방법을 대기없이 설정하는 핵심은 스레드 스케줄러가 필요할 때 각 스레드에 우선 순위를 부여한다고 가정 할 수 없다는 것입니다. 각 스레드가 단순히 자체 노드를 시퀀싱하려고 시도한 경우로드시 스레드가 계속 선점 될 수 있습니다. 이러한 가능성을 설명하기 위해 각 스레드는 먼저 시퀀싱 할 수없는 다른 스레드를 '도움'하려고합니다.
기본 아이디어는 각 스레드가 성공적으로 노드를 만들면 할당 된 시퀀스가 단조 증가한다는 것입니다. 스레드가 계속해서 다른 스레드를 선점하면 announce
배열 에서 순서가 지정되지 않은 노드를 찾는 데 사용되는 인덱스가 앞으로 이동합니다. 현재 특정 노드를 시퀀싱하려고하는 모든 스레드가 다른 스레드에 의해 지속적으로 선점 되더라도 결국 모든 스레드가 해당 노드를 시퀀싱하려고합니다. 설명하기 위해 3 개의 스레드로 예제를 구성합니다.
시작점에서 세 개의 스레드 헤드 및 알림 요소가 모두 tail
노드를 가리 킵니다 . lastSequence
각 쓰레드는 0입니다.
이 시점에서 스레드 1 이 호출로 실행됩니다. Announce 배열에서 현재 색인을 생성 할 예정인 노드 인 마지막 시퀀스 (0)를 확인합니다. 노드를 시퀀싱하고 lastSequence
1로 설정합니다.
스레드 2 는 이제 호출로 실행되며 마지막 배열 (제로)에서 Announce 배열을 확인하고 도움이 필요하지 않은지 확인하여 호출 순서를 지정합니다. 성공하면 이제 lastSequence
2로 설정됩니다.
스레드 3 이 이제 실행되고 at 노드 announce[0]
가 이미 시퀀싱되고 자체 호출 시퀀싱되는 것을 볼 수 있습니다. 그것은 것 lastSequence
해주기로 설정됩니다.
이제 스레드 1 이 다시 호출됩니다. 인덱스 1에서 Announce 배열을 확인하고 이미 시퀀싱 된 것을 찾습니다. 동시에 스레드 2 가 호출됩니다. 인덱스 2에서 Announce 배열을 확인하고 이미 시퀀싱 된 것을 찾습니다. 두 스레드 1 및 스레드 2는 이제 자신의 노드 염기 서열을 시도합니다. 스레드 2가 이기고 호출 순서입니다. 그것은 것 lastSequence
4. 한편으로 설정, 스레드 세 가지가 호출되었습니다. 인덱스를 점검하고 lastSequence
(mod 3) at 노드 announce[0]
가 시퀀싱되지 않았 음을 발견합니다. 스레드 1 이 두 번째 시도 와 동시에 스레드 2 가 다시 호출됩니다 . 실 1Thread 2에announce[1]
의해 방금 생성 된 노드 인 순서없는 호출을 찾습니다 . 스레드 2의 호출 을 시퀀싱하려고 시도 하고 성공합니다. 스레드 2 는 자신의 노드를 찾고 시퀀싱되었습니다. 5로 설정 합니다. 그런 다음 스레드 3 이 호출되고 스레드 1이있는 노드 가 여전히 시퀀싱되지 않았으며이를 시도합니다. 한편 스레드 2 도 호출되어 스레드 3을 선점합니다. 노드 2를 시퀀싱하고 6으로 설정합니다 .announce[1]
lastSequence
announce[0]
lastSequence
불쌍한 실 1 . 스레드 3 이이를 시퀀싱하려고 하더라도 스케줄러에 의해 두 스레드가 지속적으로 차단되었습니다. 그러나이 시점에서. 스레드 2 도 이제 announce[0]
(6 mod 3)을 가리 킵니다 . 세 개의 스레드 모두 동일한 호출을 시퀀싱하도록 설정되어 있습니다. 어떤 스레드가 성공하든 상관없이 시퀀싱 될 다음 노드는 스레드 1 의 대기 호출, 즉에 의해 참조되는 노드 announce[0]
입니다.
불가피합니다. 스레드를 미리 비우려면 다른 스레드가 시퀀싱 노드 여야하며 그렇게하면 계속해서 lastSequence
앞으로 나아갑니다. 주어진 스레드의 노드가 연속적으로 시퀀싱되지 않으면 결국 모든 스레드는 Announce 배열의 인덱스를 가리 킵니다. 도움을 줄 노드가 시퀀싱 될 때까지 어떤 스레드도 다른 작업을 수행하지 않습니다. 최악의 시나리오는 모든 스레드가 동일한 순서가없는 노드를 가리키는 것입니다. 따라서 호출 순서를 정하는 데 필요한 시간은 입력 크기가 아닌 스레드 수의 함수입니다.