JDK 자물쇠 의 기초 - AQS 실현 원리 (2)
protected boolean tryAcquire(int arg)
protected boolean tryRelease(int arg)
protected int tryAcquireShared(int arg)
protected boolean tryReleaseShared(int arg)
본 고 는 공유 자 물 쇠 를 얻 는 상황 을 분석 하지 않 는 다.
먼저 비교적 간단 한 하위 클래스 실현
ReentrantLock
중의 내부 클래스 FairSync
를 살 펴 보 자. 다음은 ReentrantLock
의 실현 을 직접 분석 해 보 자. ReentrantLock
한 필드 private final Sync sync;
가 있 는데 Sync
는 바로 AQS 에서 계승 한 것 이다.ReentrantLock
의 구조 함 수 를 살 펴 보 자. public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
그 중에서
NonfairSync
과 FairSync
는 모두 Sync
류 에서 계승 되 었 고 자 물 쇠 를 얻 는 방식 이 공평 한 지 의 차 이 는 다음 과 같다. //NonfairSync
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
//FairSync
final void lock() {
acquire(1);
}
이 를 통 해 알 수 있 듯 이 불공 정 자 물 쇠 는 자 물 쇠 를 가 져 올 때 자 물 쇠 를 직접 가 져 올 수 있 는 지, 가능 하 다 면 줄 을 서지 않 아 도 된다
acquire
. 공정 자 물 쇠 는 대기 열 에 직접 들 어가 줄 을 서 는 것 이다.AQS 에서
acquire
방법의 실현 을 살 펴 보 자. public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire
방법의 대체적인 논 리 는 먼저 호출 tryAcquire
하여 자 물 쇠 를 가 져 오 려 고 시도 하 는 것 이다. 실패 하면 Node
대상 을 새로 만 들 고 AQS 대기 열 에 추가 하 는 것 이다. private Node addWaiter(Node mode) {
// Node ,
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
// compareAndSetTail Node , , , enq
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// Node , enq
enq(node);
return node;
}
addWaiter
방법 으로 Node 대상 을 새로 만 들 고 EXCLUSIVE
모드 로 설정 합 니 다. 즉, 자 물 쇠 를 배열 하 는 것 입 니 다.이 방법 에 서 는 먼저 원자 조작 을 사용 하여 새 노드 를 대기 행렬 의 끝 에 추가 하려 고 시도 합 니 다. 추가 에 성공 하면 바로 돌아 갑 니 다. 그렇지 않 으 면 호출 enq()
방법 으로 추가 합 니 다. private Node enq(final Node node) {
for (;;) {
Node t = tail;
// , , 、
if (t == null) { // Must initialize
// , , if , enq node
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
// , ,
if (compareAndSetTail(t, node)) {
// next node node
t.next = node;
return t;
}
}
}
}
AQS 대기 열 은 lazy 모드 로 초기 화 되 었 습 니 다. 즉, 스 레 드 가 없 으 면 대기 열 이 만들어 지지 않 는 다 는 것 입 니 다.대기 행렬 의 초기 화 는
enq()
방법 에서 이 루어 졌 다.addWaiter
과정 에서 다른 스 레 드 가 잠 금 을 풀 었 을 수 있 기 때문에 acquireQueued
방법 에서 새 노드 를 막 기 전에 잠 금 을 가 져 오 려 고 시도 할 수 있 습 니 다. 실패 하면 이 스 레 드 를 막 습 니 다.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);
}
}
AQS 대기 열 에 있 는 헤드 노드 는 현재 자 물 쇠 를 가지 고 있 는 스 레 드 를 대표 합 니 다. 따라서 한 노드 의 전구 노드 가 헤드 노드 일 때 자 물 쇠 를 가 져 오 려 고 시도 할 수 있 습 니 다 (자 물 쇠 를 가지 고 있 는 스 레 드 가 자 물 쇠 를 풀 었 을 수 있 기 때 문 입 니 다). 자 물 쇠 를 가 져 오 는 데 성공 하면 이 노드 를 헤드 노드 로 설정 하고 되 돌려 줍 니 다.현재 노드 의 전구 노드 가 머리 노드 가 아니라면 잠 금 을 가 져 올 기 회 를 주지 않 습 니 다. AQS 대기 열 이 FIFO 이기 때문에 잠 금 을 가 져 올 차례 가 아 닙 니 다.
이 노드 가 잠 금 을 가 져 오 는 데 실패 하면 호출
shouldParkAfterFailedAcquire
방법 으로 현재 노드 를 막 아야 하 는 지 판단 합 니 다.private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// SIGNAL, ,
if (ws == Node.SIGNAL)
return true;
// CANCELLED 0
// CANCELLED,
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 0 PROPAGATE( , )
// SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
shouldParkAfterFailedAcquire
방법 이 false 로 돌아 가면 acquireQueued
방법 에서 다음 순환 shouldParkAfterFailedAcquire
방법 은 현재 노드 의 전구 노드 나 전구 노드 의 상 태 를 수정 할 수 있 기 때 문) 을 할 수 있 습 니 다. 이때 현재 노드 의 전구 노드 는 머리 노드 나 전구 노드 의 상태 가 SIGNAL
로 변 할 수 있 습 니 다.shouldParkAfterFailedAcquire
방법 이 true 로 돌아 오 면 parkAndCheckInterrupt
방법 으로 현재 노드 대표 의 스 레 드 를 막 습 니 다. private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
이로써 자 물 쇠 를 얻 는 방법
acquire
을 분석 했다.자 물 쇠 를 풀 어 주 는 방법 release
을 살 펴 보 자.public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
//
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
release
방법 호출 tryRelease
(템 플 릿 방법, 하위 클래스 에 맡 겨 실현) 은 해당 수량의 신 호 량 을 방출 합 니 다. 방법 호출 이 성공 하면 대기 행렬 헤드 노드 의 후계 노드 를 깨 웁 니 다.private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
// 0,
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
// next , next , node next , tail node
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);
}
중단 되 지 않 은 경우
unparkSuccessor
방법 에서 unpark
의 스 레 드 는 방법 acquireQueued
방법 으로 계속 작 동 되 고 parkAndCheckInterrupt
방법 은 false 로 돌아 간 다음 에 다시 자 물 쇠 를 가 져 오 려 고 시도 할 것 이다. 이번에 자 물 쇠 를 가 져 오 면 성공 할 것 이다. 그리고 acquire
방법 은 돌아 올 것 이다. 이 스 레 드 는 임계 구역 코드 를 실행 할 수 있다 는 것 을 의미한다.마지막 으로 대기 취소 방법 을 살 펴 보 겠 습 니 다. 자 물 쇠 를 가 져 올 때 시간 초과 와 시간 초과 가 설정 되 어 있 으 면 대기 취소 가 필요 합 니 다. 이 때
cancelAcquire
방법 으로 이동 합 니 다.private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;
// ,
// , , ( ), , prev
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
node.waitStatus = Node.CANCELLED;
// ,
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// pred , pred
// pred SIGNAL, pred SIGNAL , pred ,
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
// pred ,
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
// pred , ,
// pred , , acquireQueued , , SIGNAL
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.