자바 병렬 시리즈 의 AbstractQueuedSynchronizer 소스 코드 분석(독점 모델)

지난 편《자바 병발 시리즈[1]-AbstractQueuedSynchronizer 소스 코드 분석의 개요 분석》에서 우 리 는 Abstract Queued Synchronizer 의 기본 적 인 개념 을 소개 했다.주로 AQS 의 대기 구역 이 어떻게 실현 되 는 지,독점 모델 과 공유 모델 이 무엇 인지,그리고 결점 의 대기 상 태 를 어떻게 이해 하 는 지 에 대해 이야기 했다.이런 내용 을 이해 하고 파악 하 는 것 이 AQS 소스 코드 를 후속 적 으로 읽 는 관건 이기 때문에 독자 들 이 먼저 나의 지난 글 을 보고 나 서 이 편 을 돌 이 켜 보면 이해 하기 쉽다 고 건의 합 니 다.이 편 에 서 는 독점 모드 에서 결산 점 이 동기 화 대기 열 에 어떻게 들 어가 줄 을 서 는 지,동기 화 대기 열 을 떠 나 기 전에 어떤 조작 을 하 는 지 소개 한다.AQS 는 독점 모드 와 공유 모드 에서 자 물 쇠 를 가 져 오 는 데 각각 세 가지 가 져 오 는 방식 을 제공 합 니 다.스 레 드 인 터 럽 트 가 져 오 는 것 에 응답 하지 않 고 스 레 드 인 터 럽 트 가 져 오 는 것 에 응답 하 며 시간 초과 가 져 오 는 것 을 설정 합 니 다.이 세 가지 방식 의 전체적인 절 차 는 대체적으로 같 고 일부분 만 다른 부분 이 있 기 때문에 한 가지 방식 을 이해 하고 다른 방식 의 실현 을 보면 모두 대동소이 하 다.이 편 에서 저 는 스 레 드 중단 에 응 하지 않 는 획득 방식 을 다시 이야기 하고 다른 두 가지 방식 도 일치 하지 않 는 부분 을 말씀 드 리 겠 습 니 다.
1.어떻게 응답 하지 않 는 라인 으로 자 물 쇠 를 가 져 옵 니까?

//         (    )
public final void acquire(int arg) {
  if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
    selfInterrupt();
  }
}
위의 코드 는 간단 해 보이 지만 다음 그림 에서 보 여 준 4 단 계 를 순서대로 수행 했다.다음은 단계별 로 시연 분석 을 진행 하 겠 습 니 다.

STEP 1:!tryAcquire(arg)

 //      (    )
protected boolean tryAcquire(int arg) {
  throw new UnsupportedOperationException();
}
이때 한 사람 이 왔 다.그 는 먼저 문 을 두 드 려 보 았 다.문 이 잠 겨 있 지 않 은 것 을 발견 하면 바로 들 어 갔다.문 이 잠 겨 있 는 것 을 발견 하면 다음 단 계 를 수행 합 니 다.이 try Acquire 방법 은 언제 잠 겨 있 는 지,언제 잠 겨 있 는 지 를 결정 합 니 다.이 방법 은 하위 클래스 를 덮어 쓰 고 판단 논 리 를 다시 써 야 한다.
두 번 째 단계:addWaiter(Node.EXCLUSIVE)

//                    
private Node addWaiter(Node mode) {
  //        
  Node node = new Node(Thread.currentThread(), mode);
  //           
  Node pred = tail;
  //        ,            
  if (pred != null) {
    //1.       
    node.prev = pred;
    //2.          
    if (compareAndSetTail(pred, node)) {
      //3.                
      pred.next = node;
      return node;
    }
  }
  //                
  enq(node);
  return node;
}

//      
private Node enq(final Node node) {
  for (;;) {
    //           
    Node t = tail;
    //                   
    if (t == null) {
      //       
      if (compareAndSetHead(new Node())) {
        tail = head;
      }
    } else {
      //1.       
      node.prev = t;
      //2.          
      if (compareAndSetTail(t, node)) {
        //3.                
        t.next = node;
        return t;
      }
    }
  }
}
이 단 계 를 실행 하면 처음으로 자 물 쇠 를 가 져 오 는 데 실 패 했 음 을 나타 낸다.그러면 이 사람 은 자신 에 게 번호 표를 받 고 줄 을 섰 다.번호 표를 받 을 때 자신 이 어떤 방식 으로 방 을 점용 하고 싶 은 지 설명 한다(독점 모드 or 공유 모드).주의 하 세 요.이때 그 는 앉 아서 쉬 지 않 았 습 니 다.
STEP 3:acquireQueued(addWaiter(Node.EXCLUSIVE),arg)

//          (    )
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)) {
        //        head  
        setHead(node);
        //        ,     head       
        p.next = null;
        //        
        failed = false;
        //       ,              
        return interrupted;
      }
      //              ,               
      //               ,       ,            
      if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
        interrupted = true;
      }
    }
  } finally {
    //                
    if (failed) {
      cancelAcquire(node);
    }
  }
}

//             
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
  //           
  int ws = pred.waitStatus;
  //         SIGNAL,              ,               
  if (ws == Node.SIGNAL) {
    return true;
  }
  
  if (ws > 0) {
    //                       
    do {
      node.prev = pred = pred.prev;
    } while (pred.waitStatus > 0);
    pred.next = node;
  } else {
    //             SIGNAL,        0,                    
    //                   SIGNAL         
    compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
  }
  return false;
}

//      
private final boolean parkAndCheckInterrupt() {
  LockSupport.park(this);
  return Thread.interrupted();
}
번호표 받 고 줄 서기 구역 에 들 어가 면 바로 이 방법 을 집행 한다.한 노드 가 처음으로 줄 서기 구역 에 들 어간 후에 두 가지 상황 이 있다.하 나 는 그의 앞 에 있 는 사람 이 이미 자 리 를 떠 나 방 에 들 어간 것 을 발견 하면 그 는 앉 아서 쉬 지 않 고 다시 문 을 두 드 려 그 녀석 이 일 을 끝 냈 는 지 볼 것 이다.만약 안에 있 는 사람 이 마침 일 을 끝내 고 나 왔 다 면,그 가 자신 을 부 르 지 않 고 바로 뛰 어 들 었 을 것 이다.그렇지 않 으 면 앉 아서 좀 쉬 는 것 을 고려 해 야 하 는데,그 는 여전히 마음 이 놓 이지 않 는 다.만약 그 가 앉 아서 잠 든 후에 아무 도 그 에 게 어떻게 하 라 고 일 깨 워 주지 않 는 다 면?그 는 안에서 나 온 사람 이 쪽 지 를 보고 깨 울 수 있 도록 앞 자리 에 작은 쪽 지 를 남 겼 다.또 한 가지 상황 은 그 가 줄 을 서 있 는 구역 에 들 어간 후에 앞 에 몇 사람 이 자리 에서 줄 을 서 있 는 것 을 발견 하면 안심 하고 앉 아서 잠시 미 소 를 지 을 수 있 지만 그 전에 그 는 앞 에 있 는 사람(이때 이미 잠 들 었 다)의 자리 에 쪽 지 를 남 겨 서 이 사람 이 가기 전에 자신 을 깨 울 수 있 도록 하 는 것 이다.모든 일이 다 끝 난 후에 그 는 편안하게 잠 을 잤 다.우 리 는 전체 for 순환 이 하나의 출구 만 있 는 것 을 보 았 다.그것 은 바로 스 레 드 가 성공 적 으로 자 물 쇠 를 얻 은 후에 나 갈 수 있 고 자 물 쇠 를 얻 지 못 하기 전에 for 순환 의 Park AndCheck Interrupt()방법 에 걸 려 있 었 다.스 레 드 가 깨 어 난 후에 도 이곳 에서 for 순환 을 계속 실행 합 니 다.
STEP 4:selfInterrupt()

 //         
 private static void selfInterrupt() {
   Thread.currentThread().interrupt();
 }
위의 전체 스 레 드 는 for 순환 하 는 파 크 앤 드 체크 인 터 럽 트()방법 에 걸 려 있 기 때문에 자 물 쇠 를 얻 기 전에 어떠한 형식의 스 레 드 인 터 럽 트 에 도 응답 하지 않 습 니 다.스 레 드 가 자 물 쇠 를 성공 적 으로 얻 고 for 순환 에서 나 온 후에 야 스 레 드 를 중단 하 라 는 사람 이 있 는 지 확인 할 수 있 습 니 다.그렇다면 self Interrupt()방법 을 사용 하여 자신 을 걸 수 있 습 니 다.
2.어떻게 응답 스 레 드 인 터 럽 트 로 자 물 쇠 를 가 져 옵 니까?

//         (    )
private void doAcquireInterruptibly(int arg) throws InterruptedException {
  //                  
  final Node node = addWaiter(Node.EXCLUSIVE);
  boolean failed = true;
  try {
    for (;;) {
      //           
      final Node p = node.predecessor();
      //  p head  ,               
      if (p == head && tryAcquire(arg)) {
        setHead(node);
        p.next = null; // help GC
        failed = false;
        //        
        return;
      }
      //             ,            
      if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
        //                   
        throw new InterruptedException();
      }
    }
  } finally {
    if (failed) {
      cancelAcquire(node);
    }
  }
}
응답 스 레 드 인 터 럽 트 방식 과 응답 하지 않 는 스 레 드 인 터 럽 트 방식 으로 잠 금 을 가 져 오 는 절 차 는 대체적으로 같 습 니 다.유일한 차이 점 은 스 레 드 가 파 크 앤 드 체크 인 터 럽 트 방법 에서 깨 어 난 후에 스 레 드 가 중단 되 었 는 지 확인 하 는 것 입 니 다.그렇다면 Interrupted Exception 이상 을 던 지고 스 레 드 중단 에 응 하지 않 고 자 물 쇠 를 가 져 오 는 것 은 중단 요청 을 받 은 후에 중단 상 태 를 설정 하 는 것 입 니 다.현재 자 물 쇠 를 가 져 오 는 방법 을 즉시 끝내 지 않 습 니 다.자 물 쇠 를 성공 적 으로 가 져 올 때 까지 중단 상태 에 따라 자신 을 걸 지 여 부 를 결정 합 니 다.
3.시간 초과 로 자 물 쇠 를 가 져 오 는 방법 을 설정 합 니까?

//          (    )
private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
  //        
  long lastTime = System.nanoTime();
  //                  
  final Node node = addWaiter(Node.EXCLUSIVE);
  boolean failed = true;
  try {
    for (;;) {
      //           
      final Node p = node.predecessor();
      //     head  ,               
      if (p == head && tryAcquire(arg)) {
        //  head  
        setHead(node);
        p.next = null;
        failed = false;
        return true;
      }
      //              
      if (nanosTimeout <= 0) {
        return false;
      }
      //            ,                         
      if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold) {
        //           ,        
        LockSupport.parkNanos(this, nanosTimeout);
      }
      //        
      long now = System.nanoTime();
      //                 
      nanosTimeout -= now - lastTime;
      //    lastTime
      lastTime = now;
      //                  
      if (Thread.interrupted()) {
        throw new InterruptedException();
      }
    }
  } finally {
    if (failed) {
      cancelAcquire(node);
    }
  }
}
시간 초과 가 져 오기 설정 을 하면 먼저 자 물 쇠 를 가 져 옵 니 다.첫 번 째 자 물 쇠 를 가 져 오 는 데 실패 한 후 상황 에 따라 시간 초과 가 자전 시간 보다 많 으 면 스 레 드 를 일정 시간 걸 고 그렇지 않 으 면 자전 을 합 니 다.자 물 쇠 를 가 져 올 때마다 시간 초과 시간 을 한 번 의 자 물 쇠 를 가 져 오 는 데 걸 리 는 시간 을 줄 입 니 다.시간 이 0 보다 적 을 때 까지 시간 이 초과 되 었 다 는 것 은 시간 이 다 되 었 다 는 것 을 의미 합 니 다.그러면 이 때 는 자 물 쇠 를 가 져 오 는 작업 을 마치 고 실패 표 지 를 가 져 옵 니 다.시간 초과 로 자 물 쇠 를 가 져 오 는 과정 에서 스 레 드 중단 요청 에 응답 할 수 있 음 을 주의 하 십시오.
4.스 레 드 가 자 물 쇠 를 풀 고 동기 화 대기 열 을 떠 나 는 것 은 어떻게 진행 되 나 요?

//      (    )
public final boolean release(int arg) {
  //     ,         
  if (tryRelease(arg)) {
    //  head  
    Node h = head;
    //  head              0        
    if (h != null && h.waitStatus != 0) {
      //      
      unparkSuccessor(h);
    }
    return true;
  }
  return false;
}

//      
private void unparkSuccessor(Node node) {
  //           
  int ws = node.waitStatus;
  //        0
  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);
  }
}
스 레 드 는 자 물 쇠 를 가지 고 방 에 들 어가 면 자신의 일 을 하고 일이 끝 난 후에 자 물 쇠 를 풀 고 방 을 떠난다.tryRelease 방법 을 통 해 암호 자 물 쇠 를 눌 러 잠 금 을 풀 수 있 습 니 다.tryRelease 방법 은 하위 클래스 를 덮어 야 한 다 는 것 을 알 고 있 습 니 다.서로 다른 하위 클래스 가 실현 하 는 규칙 이 다 릅 니 다.즉,서로 다른 하위 클래스 가 설정 한 암호 가 다르다 는 것 입 니 다.ReentrantLock 에서 방 안에 있 는 사람 이 try Release 방법 을 호출 할 때마다 state 는 1 을 줄 이 고 state 가 0 으로 줄 어 들 때 까지 비밀번호 자물쇠 가 열 렸 다.여러분 은 이 과정 이 우리 가 끊임없이 비밀번호 자 물 쇠 를 돌 리 는 회전 바퀴 와 같 지 않 고 매번 회전 바퀴 숫자 가 1 만 줄 어드 는 것 과 같다 고 생각 하 세 요.Count Downlatch 는 이것 과 비슷 합 니 다.단지 한 사람 이 돌아 가 는 것 이 아니 라 여러 사람 이 돌아 가 는 것 입 니 다.여러분 의 힘 을 모 아 자 물 쇠 를 열 었 습 니 다.스 레 드 가 방 을 나 간 후에 그것 은 자신의 원래 의 자 리 를 찾 을 것 이다.즉,헤드 노드 를 찾 는 것 이다.자리 에 작은 쪽 지 를 남 긴 사람 이 있 는 지 살 펴 보 자.누 군가 잠 들 었 다 는 것 을 알 고 깨 워 달라 고 하면 그 스 레 드 를 깨 울 것 이다.없 으 면 동기 화 대기 열 에서 아직 기다 리 는 사람 이 없고 깨 워 야 할 사람 도 없 기 때문에 안심 하고 떠 날 수 있다 는 뜻 이다.이상 의 과정 은 독점 모드 에서 자 물 쇠 를 풀 어 주 는 과정 이다.
주의:상기 모든 분석 은 JDK 1.7 을 바탕 으로 버 전 간 에 차이 가 있 을 수 있 으 므 로 독자 들 은 주의해 야 합 니 다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기