[실전 자바 고 병발 프로 그래 밍 7]스 레 드 간 에 서로 돕 기--SynchronousQueue 의 실현

스 레 드 풀 에 대한 소개 에서 매우 특별한 대기 열 SynchronousQueue 를 언급 하 였 습 니 다.SynchronousQueue 의 용량 은 0 입 니 다.SynchronousQueue 에 대한 모든 쓰 기 는 SynchronousQueue 에 대한 읽 기 를 기 다 려 야 합 니 다.반대로 도 마찬가지 입 니 다.따라서 SynchronousQueue 는 하나의 대기 열 이 라 기보 다 는 데이터 교환 채널 이다.그렇다면 SynchronousQueue 의 묘 한 기능 은 어떻게 이 루어 졌 을 까?
     내 가 이 절 에서 그것 을 소개 하려 고 하 는 이상 SynchronousQueue 는 예 를 들 어 잠 금 이 없 는 조작 과 관 계 를 끊 을 수 없다.실제로 SynchronousQueue 내부 에서 도 자물쇠 없 는 도 구 를 대량으로 사용 하고 있 습 니 다.
SynchronousQueue 에 있어 서 put()와 take()두 기능 이 전혀 다른 조작 을 하나의 공 통 된 방법 인 Transferer.transfer()로 추상 화 합 니 다.말 그대로 데이터 전달 이라는 뜻 이다.전체 서명 은 다음 과 같 습 니 다.
Object transfer(Object e, boolean timed, long nanos)

  매개 변수 e 가 비어 있 지 않 을 때 현재 작업 이 한 소비자 에 게 전달 되 는 것 을 나타 내 고 비어 있 으 면 현재 작업 에 데 이 터 를 요청 해 야 한 다 는 것 을 나타 낸다.timed 매개 변 수 는 timeout 시간 이 존재 하 는 지 여 부 를 결정 합 니 다.nanos 는 timeout 의 시간 을 결정 합 니 다.반환 값 이 비어 있 지 않 으 면 데이터 및 수락 또는 정상 제공 을 표시 하고 비어 있 으 면 실패(시간 초과 또는 중단)를 표시 합 니 다.
  SynchronousQueue 내부 에서 스 레 드 대기 열 을 유지 합 니 다.대기 열 에 대기 스 레 드 와 관련 된 데 이 터 를 저장 합 니 다.예 를 들 어 생산자 가 데 이 터 를 SynchronousQueue 에 넣 을 때 소비자 가 받 아들 이지 않 으 면 데이터 자체 와 스 레 드 대상 이 대기 열 에 포장 되 어 기 다 립 니 다(SynchronousQueue 용적 이 0 이기 때문에 정상적으로 넣 을 수 있 는 데이터 가 없습니다).
Transferer.transfer()함수 의 실현 은 SynchronousQueue 의 핵심 으로 크게 세 단계 로 나 뉜 다.
1.대기 열 이 비어 있 거나 대기 열 에 있 는 노드 의 유형 이 이번 작업 과 일치 하면 현재 작업 을 대기 열 에 눌 러 대기 합 니 다.예 를 들 어 대기 열 에 서 는 읽 기 스 레 드 가 기다 리 고 이번 작업 도 읽 기 때문에 이 두 개의 읽 기 를 기 다 려 야 합 니 다.대기 열 에 들 어 가 는 스 레 드 가 걸 릴 수 있 습 니 다.'일치'동작 을 기다 리 고 있 습 니 다.
2.대기 열 에 있 는 요소 와 이번 작업 이 서로 보완 된다 면(예 를 들 어 대기 동작 은 읽 기 이 고 이번 작업 은 쓰기)'완료'상태의 노드 를 삽입 하고 대기 노드 에'일치'하도록 합 니 다.이 어 이 두 노드 를 팝 업 하고 해당 하 는 두 스 레 드 를 계속 실행 합 니 다.
3.스 레 드 가 대기 행렬 의 노드 를 발견 하면'완성'노드 입 니 다.그럼 이 노드 가 임 무 를 완성 하도록 도와 주세요.그 절차 와 절차 2 는 일치한다.
 STEP 1 의 실현 은 다음 과 같다(코드 참조 JDK 7u 60).
SNode h = head;
if (h == null || h.mode == mode) {  				//       ,      
    if (timed && nanos <= 0) {      				//      
        if (h != null && h.isCancelled())
            casHead(h, h.next);     			//       
        else
            return null;
    } else if (casHead(h, s = snode(s, e, h, mode))) {
        SNode m = awaitFulfill(s, timed, nanos); 	//  ,         
        if (m == s) {               				//      
            clean(s);
            return null;
        }
        if ((h = head) != null && h.next == s)
            casHead(h, s.next);     			//   s  fulfiller
        return (mode == REQUEST) ? m.item : s.item;
    }
}

   위 코드 에서 첫 번 째 줄 SNode 는 대기 열의 노드 를 표시 합 니 다.내부 에 현재 스 레 드,next 노드,일치 하 는 노드,데이터 내용 등 정 보 를 밀봉 했다.두 번 째 줄 은 현재 대기 열 이 비어 있 거나 대기 열 에 있 는 요소 의 패턴 이 이번 작업 과 같다 고 판단 합 니 다(예 를 들 어 읽 기 동작 이 라면 모두 기 다 려 야 합 니 다).여덟 번 째 줄 은 새로운 노드 를 만 들 고 대기 열 머리 에 두 면 이 노드 는 현재 스 레 드 를 대표 합 니 다.입단 에 성공 하면 9 번 째 줄 await Fulfill()함 수 를 실행 합 니 다.이 함 수 는 자전 대기 하고 최종 적 으로 현재 스 레 드 를 걸 것 입 니 다.그 에 대응 하 는 조작 이 생 길 때 까지 깨 워 라.스 레 드 가 깨 어 난 후(데 이 터 를 읽 었 거나 자신 이 만 든 데 이 터 를 다른 스 레 드 에서 읽 었 음 을 표시 합 니 다)14~15 줄 에서 해당 하 는 스 레 드 가 두 머리 노드 의 출하 작업 을 완성 하 는 데 도움 을 주 려 고 시도 합 니 다(이것 은 우정 도움 일 뿐 입 니 다).마지막 으로 읽 거나 기록 한 데이터(16 줄)를 되 돌려 줍 니 다.
 단계 2 의 실현 은 다음 과 같다.
} else if (!isFulfilling(h.mode)) { 			//    fulfill  
    if (h.isCancelled())           		//        
        casHead(h, h.next);         	//      
    else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
        for (;;) {                        //         (match)        
            SNode m = s.next;       	// m   s    (match)
            if (m == null) {        		//         
                casHead(s, null);   		//   fulfill  
                s = null;           		//          
                break;              	//        
            }
            SNode mn = m.next;
            if (m.tryMatch(s)) {
                casHead(s, mn);     	//   s   m
                return (mode == REQUEST) ? m.item : s.item;
            } else                  	// match   
                s.casNext(m, mn);   	//       
        }
    }
}

   상기 코드 에서 먼저 머리 노드 가 fulfull 모드 에 있 는 지 판단 합 니 다.그렇다면,3 단계 로 들 어가 야 한다.그렇지 않 으 면 자신 이 대응 하 는 fulfilly 스 레 드 를 시도 합 니 다.네 번 째 줄 은 SNode 요 소 를 생 성하 여 fulfilly 모드 로 설정 하고 대기 열 머리 에 눌 러 줍 니 다.이 어 m(원본 대기 열 머리)를 s 의 일치 노드(13 줄)로 설정 합 니 다.이 try Match()작업 은 대기 스 레 드 를 활성화 시 키 고 m 를 그 스 레 드 에 전달 합 니 다.설정 이 성공 하면 데이터 배달 이 완료 되 었 음 을 나타 내 고 s 와 m 두 노드 를 팝 업 하면 됩 니 다(14 줄).try Match()가 실패 하면 다른 스 레 드 가 작업 을 마 쳤 다 는 뜻 입 니 다.m 노드 를 간단하게 삭제 하면 됩 니 다(17 줄).이 노드 의 데이터 가 배달 되 었 기 때문에 다시 처리 할 필요 가 없습니다.그리고 5 줄 의 순환 체 로 다시 이동 하여 다음 대기 스 레 드 의 일치 와 데이터 배달 을 진행 합 니 다.대기 열 에서 스 레 드 를 기다 리 지 않 을 때 까지.
 STEP 3:스 레 드 가 실 행 될 때 머리 요소 가 fulfull 모드 인 것 을 발견 하면 이 fulfull 노드 가 빨리 실 행 될 수 있 도록 도와 줄 것 입 니 다.
} else {                                            //      fulfiller
    SNode m =h.next;                           // m   h  match
    if (m ==null)                                   //      
        casHead(h,null);                          //   fulfill  
    else {
        SNode mn =m.next;
        if(m.tryMatch(h))                           //    match
           casHead(h, mn);                     //    h   m
        else                                    // match  
            h.casNext(m,mn);                    //       
    }
}

   상기 코드 의 집행 원리 와 절차 2 는 완전히 일치한다.유일한 차이 점 은 절차 3 이 되 돌아 오지 않 는 다 는 것 이다.절차 3 이 진행 하 는 작업 은 다른 스 레 드 가 가능 한 한 빨리 데 이 터 를 배달 하도록 도와 주 는 것 이기 때문이다.자신 이 해당 하 는 조작 을 완성 하지 못 했 기 때문에 스 레 드 가 3 단계 에 들 어간 후에 다시 큰 순환 체(코드 에 제시 되 지 않 았 음)에 들 어가 1 단계 부터 조건 과 배달 데 이 터 를 다시 판단 한다.
    전체 데 이 터 를 배달 하 는 과정 에서 볼 수 있 듯 이 SynchronousQueue 에서 작업 에 참여 하 는 모든 스 레 드 는 경쟁 자원 의 관계 만 이 아니다.더 중요 한 것 은 서로 돕 는 것 이다.하나의 스 레 드 내부 에 서 는 다른 스 레 드 가 작업 을 완성 하 는 데 도움 이 될 수 있 습 니 다.이런 모델 은 배 고 픔 의 가능성 을 어느 정도 줄 이 고 시스템 전체의 병행 도 를 높 일 수 있다.
[실전 자바 고 병발 프로 그래 밍 1]자바 의 지침:Unsafe 류
[실전 자바 고 병발 프로 그래 밍 2]잠 금 없 는 대상 참조:AtomicReference
[실전 자바 고 병발 프로 그래 밍 3]타임 스탬프 가 있 는 대상 참조:AtomicStamped Reference
[실전 자바 고 병발 프로 그래 밍 4]배열 도 잠 금 없 음:AtomicIntegerArray
[실전 자바 고 병발 프로 그래 밍 5]일반 변수 도 원자 조작 을 즐 길 수 있 도록 합 니 다.
[실전 자바 고 병발 프로 그래 밍 6]도전 무 잠 금 알고리즘
실전 자바 고 병발 프로 그래 밍

좋은 웹페이지 즐겨찾기