높 은 병발 (24) 자바 util. concurrent 각 구성 요소 분석 (6) AQS 깊이 이해 (4)
9203 단어 병발 하 다
AQS 는 하나의 파이프라인 으로 기본 적 인 동기 화 기 를 제공 하 는 능력 을 제공 합 니 다. 하나의 상태, 상 태 를 수정 하 는 원자 조작, 그리고 동기 화 라인 의 일련의 조작 을 포함 합 니 다.CLHLock 의 변종 입 니 다. CLHLock 은 대기 열 잠 금 을 기반 으로 하 는 자동 잠 금 알고리즘 입 니 다.AQS 도 동기 화 스 레 드 의 구조 로 대기 열 을 사 용 했 습 니 다. 하 나 는 스 레 드 동기 화 된 동기 화 대기 열 이 고 다른 하 나 는 Unsafe 를 기반 으로 차단 / 깨 우기 작업 을 하 는 조건 부 대기 열 입 니 다.그래서 대기 열 조작 을 이해 하 는 것 이 AQS 를 이해 하 는 관건 이다.
1. 헤드 이해, tail 인용
2. next 이해, prev 인용
3. 대기 열 노드 가 언제 입 대 했 는 지, 언제 출전 했 는 지 이해 하기
헤드 인용 에 대해 기억 해 야 할 것 은
1. 헤드 인용 은 항상 자 물 쇠 를 얻 은 노드 를 가리 키 며 취소 되 지 않 습 니 다.acquire 작업 이 성공 하면 자 물 쇠 를 얻 었 다 는 뜻 입 니 다. acquire 과정 에서 중단 되면 acquire 가 실 패 했 습 니 다. 이때 head 는 다음 노드 를 가리 키 게 됩 니 다.
* because the head node is never cancelled: A node becomes
* head only as a result of successful acquire. A
* cancelled thread never succeeds in acquiring, and a thread only
* cancels itself, not any other node.
자 물 쇠 를 얻 은 후에 스 레 드 가 중단 되면 release 로 head 노드 를 풀 어야 합 니 다.스 레 드 가 중단 되 어 자 물 쇠 를 풀 지 않 으 면 문제 가 생 길 수 있 습 니 다.따라서 명시 적 자 물 쇠 를 사용 할 때 는 finally 에서 자 물 쇠 를 풀 어야 합 니 다.
Lock lock = new ReentrantLock();
lock.lock();
try{
// , , finally
}finally{
lock.unlock();
}
자 물 쇠 를 얻 을 때 헤드 에 대한 인용 처 리 를 살 펴 보 자. 노드 의 전구 노드 가 헤드 일 때 만 자 물 쇠 를 얻 을 수 있 고 자 물 쇠 를 얻 은 후에 자신 을 헤드 노드 로 설정 하 는 동시에 오래된 헤드 의 next 를 null 로 설정 해 야 한다.
여기에 몇 가지 의미 가 있다.
1. 처음부터 끝까지 헤드 노드 부터 잠 금 획득
2. 새로운 스 레 드 에서 자 물 쇠 를 얻 은 후, 이전에 자 물 쇠 를 얻 은 노드 가 대기 열 에서 나 옵 니 다.
3. 자 물 쇠 를 얻 으 면 acquire 방법 은 반드시 되 돌아 갑 니 다. 이 과정 에서 중단 되 지 않 습 니 다.
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
tail 인용 에 대해 서 는 체인 구 조 를 잠 금 없 이 실현 하고 CAS + 폴 링 방식 을 사용 합 니 다.노드 의 입단 작업 은 모두 tail 노드 에 있다.
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
next 인용 은 대기 열 에서 매우 중요 한 역할 을 하 는데 나타 나 는 빈도 가 매우 높다.next 참조 에 대하 여 몇 가지 값 이 있 습 니 다.
1. next = null
2. next 는 null 이 아 닌 다음 노드 를 가리킨다.
3. next = 노드 자신
next = null 의 경 우 는 세 가지 가 있 습 니 다.
1. 팀 끝 노드, 팀 끝 노드 의 next 는 명시 적 으로 설정 되 지 않 았 기 때문에 null 입 니 다.
2. 팀 의 끝 노드 가 대기 열 에 들 어 갈 때 이전 팀 의 끝 노드 next 노드 는 null 일 수 있 습 니 다. enq 는 원자 조작 이 아니 기 때문에 CAS 다음 에 복합 작업 입 니 다.
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
// next null
t.next = node;
return t;
}
}
}
}
3. 자 물 쇠 를 가 져 올 때 이전에 자 물 쇠 를 가 져 온 노드 의 next 는 null 로 설정 합 니 다.
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
next 는 null 이 아 닌 다음 노드 를 가리 키 고 있 습 니 다. 이러한 상황 은 동기 화 대기 열 에서 기다 리 는 노드 입 니 다. 입 대 를 할 때 이전 노드 의 next 값 을 설정 하면 자 물 쇠 를 풀 때 다음 노드 에 자 물 쇠 를 가 져 올 수 있 습 니 다.
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
next 는 자신 을 가 리 킵 니 다. 이것 은 작업 을 취소 할 때 노드 의 앞 노드 를 뒤의 노드 로 가리 키 고 마지막 으로 next 도 메 인 을 자신 으로 설정 합 니 다.
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
node.thread = null;
// Skip cancelled predecessors
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
prev 인용 은 비교적 간단 합 니 다. 주로 링크 구 조 를 유지 합 니 다.CLHLock 은 이전 노드 의 상태 에서 자전 합 니 다. AQS 안의 노드 는 이전 상태 에서 기다 리 는 것 이 아니 라 풀 릴 때 이전 노드 알림 대기 열 에서 다음 깨 울 노드 를 찾 습 니 다.
마지막 으로 노드 가 대기 열 에 들 어가 고 대기 열 에 나 가 는 상황 을 말 합 니 다.
노드 가 대기 열 에 들 어 가 는 것 은 한 가지 상황 입 니 다. 그것 은 바로 try Acquire 작업 이 실 패 했 고 자 물 쇠 를 얻 지 못 하면 동기 화 대기 열 에 들 어가 기 를 기다 리 는 것 입 니 다. try Acquire 가 성공 하면 동기 화 대기 열 에 들 어가 지 않 아 도 됩 니 다.AQS 는 충분 한 유연성 을 제공 합 니 다. try Acquire 와 try Relase 방법 을 제공 하여 하위 클래스 의 확장 을 제공 합 니 다. 기본 클래스 는 대기 열 작업 을 유지 하고 하위 클래스 는 스스로 대기 열 에 들 어 갈 지 여 부 를 결정 할 수 있 습 니 다.
그래서 실제 하위 클래스 가 확 장 될 때 두 가지 유형 이 있 는데 하 나 는 공평 한 동기 화 장치 이 고 하 나 는 불공평 한 동기 화 장치 이다.여기 서 주의해 야 할 것 은 이른바 불공평 하 다 는 것 이다. 대기 열 을 사용 하지 않 고 차단 작업 을 유지 하 는 것 이 아니 라 경쟁 을 얻 을 때 먼저 오 는 스 레 드 를 고려 하지 않 고 나중에 스 레 드 는 자원 을 직접 경쟁 할 수 있다 는 것 이다.불공평 하고 공평 한 동기 화기 경쟁 이 실패 한 후 에는 AQS 동기 화 대기 열 에 들 어가 기 를 기 다 려 야 하 며, 동기 화 대기 열 은 먼저 서 비 스 를 하 는 공평 한 대기 열 이다.
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
/**
* Fair version
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
FairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
대열 을 나 가 는 데 는 두 가지 상황 이 있 는데,
1. 다음 스 레 드 에서 자 물 쇠 를 얻 는 것 은 head 가 현재 자 물 쇠 를 얻 은 스 레 드 를 참조 하고 이전 자 물 쇠 를 얻 은 노드 가 자동 으로 대기 열 을 나 오 는 것 입 니 다.
2. 작업 을 취소 할 때 노드 가 대기 열 에 나 가 고 취 소 는 두 가지 상황 만 있 습 니 다. 하 나 는 스 레 드 가 중단 되 고 다른 하 나 는 시간 이 초과 되 기 를 기다 리 는 것 입 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
java. util. concurrent 패키지 의 해체@Date 2019-7-8 11:25*/public class BlockingQueueExample {public static void main(String[] args) {BlockingQueue blockingQ...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.