자바 병렬 시리즈 의 CyclicBarrier 소스 코드 분석

현실 생활 에서 우 리 는 이런 광경 을 자주 만난다.어떤 활동 을 하기 전에 사람들 이 모두 모이 기 를 기 다 려 야 시작한다.예 를 들 어 밥 을 먹 을 때 는 온 가족 이 모두 자리 에 앉 을 때 까지 기 다 려 야 젓가락 을 들 수 있 고 여행 할 때 는 모두 가 도착 한 후에 출발 해 야 하 며 경 기 를 할 때 는 선수 들 이 모두 등장 한 후에 야 시작 해 야 한다.JUC 패키지 에서 동기 화 도구 류 를 제공 하여 이러한 장면 을 잘 모 의 할 수 있 습 니 다.이것 이 바로 Cyclic Barrier 류 입 니 다.Cyclic Barrier 류 를 이용 하여 하나의 스 레 드 가 서로 기다 리 고 모든 스 레 드 가 특정한 장벽 에 도착 한 후에 후속 작업 을 할 수 있 습 니 다.다음 그림 은 이 과정 을 보 여 준다.

Cyclic Barrier 류 내부 에 카운터 가 있 습 니 다.모든 스 레 드 는 장벽 에 도 착 했 을 때 await 방법 으로 자신 을 막 습 니 다.이때 계수 기 는 1 을 줄 이 고 카운터 가 0 으로 줄 었 을 때 await 방법 으로 막 힌 모든 스 레 드 가 깨 어 납 니 다.이것 이 바로 하나의 스 레 드 가 서로 기다 리 는 원 리 를 실현 하 는 것 입 니 다.다음은 Cyclic Barrier 에 어떤 멤버 변수 가 있 는 지 살 펴 보 겠 습 니 다.

//     
private final ReentrantLock lock = new ReentrantLock();
//     
private final Condition trip = lock.newCondition();
//        
private final int parties;
//        
private final Runnable barrierCommand;
//        
private Generation generation = new Generation();
//   
private int count;

//     Generation
private static class Generation {
  boolean broken = false;
}

위 에 Cyclic Barrier 의 모든 구성원 변 수 를 붙 여 놓 았 습 니 다.Cyclic Barrier 내 부 는 조건 부 대기 열 트 립 을 통 해 스 레 드 를 막 고 내부 에 두 개의 int 형 변수 parties 와 count 를 유지 하고 있 습 니 다.parties 는 차단 할 때마다 스 레 드 수 를 표시 합 니 다.이 값 은 구성 할 때 할당 합 니 다.count 는 내부 카운터 입 니 다.초기 값 은 파티 와 같 습 니 다.이후 await 방법 이 호출 될 때마다 1 을 줄 이 고 0 으로 줄 일 때 까지 모든 스 레 드 를 깨 웁 니 다.Cyclic Barrier 는 정적 내부 클래스 Generation 이 있 습 니 다.이러한 대상 은 울타리 의 현재 세 대 를 대표 합 니 다.마치 게임 을 할 때 대표 하 는 이 게임 처럼 순환 대기 가 가능 합 니 다.barrierCommand 는 교체 전에 실 행 된 임 무 를 표시 합 니 다.count 가 0 으로 줄 었 을 때 이 게임 이 끝 났 음 을 표시 하고 다음 게임 으로 넘 어가 야 합 니 다.다음 게임 으로 넘 어가 기 전에 모든 막 힌 스 레 드 를 깨 웁 니 다.모든 스 레 드 를 깨 우기 전에 지정 한 barrierCommand 를 통 해 자신의 임 무 를 수행 할 수 있 습 니 다.다음은 구조 기 를 살 펴 보 자.

//   1
public CyclicBarrier(int parties, Runnable barrierAction) {
  if (parties <= 0) throw new IllegalArgumentException();
  this.parties = parties;
  this.count = parties;
  this.barrierCommand = barrierAction;
}

//   2
public CyclicBarrier(int parties) {
  this(parties, null);
}
Cyclic Barrier 는 두 개의 구조 기 가 있 습 니 다.그 중에서 구조 기 1 은 핵심 구조 기 입 니 다.여기 서 이 게임 의 참여 자 수(차단 할 스 레 드 수)와 이 게임 이 끝 날 때 수행 할 작업 을 지정 할 수 있 습 니 다.카운터 count 의 초기 값 이 파티 로 설정 되 어 있 는 것 도 볼 수 있 습 니 다.Cyclic Barrier 류 의 가장 주요 한 기능 은 장벽 점 에 먼저 도착 하 는 스 레 드 를 막 고 뒤의 스 레 드 를 기다 리 는 것 입 니 다.그 중에서 두 가지 기다 리 는 방법 을 제공 합 니 다.각각 정시 대기 와 비정 기적 대기 입 니 다.

//     
public int await() throws InterruptedException, BrokenBarrierException {
  try {
    return dowait(false, 0L);
  } catch (TimeoutException toe) {
    throw new Error(toe);
  }
}

//    
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException {
  return dowait(true, unit.toNanos(timeout));
}
정 해진 시간 에 기다 리 든 비정 기적 으로 기다 리 든 dowait 방법 을 사용 하 는 것 을 볼 수 있 습 니 다.들 어 오 는 매개 변수 가 다 를 뿐 입 니 다.다음은 dowait 방법 이 무엇 을 했 는 지 살 펴 보 겠 습 니 다.

//      
private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
  final ReentrantLock lock = this.lock;
  lock.lock();
  try {
    final Generation g = generation;
    //           
    if (g.broken) {
      throw new BrokenBarrierException();
    }
    //           
    if (Thread.interrupted()) {
      //                
      //1.      
      //2.         
      //3.      
      breakBarrier();
      throw new InterruptedException();
    }
    //          1
    int index = --count;
    //       0               
    if (index == 0) {
      boolean ranAction = false;
      try {
        //               
        final Runnable command = barrierCommand;
        if (command != null) {
          command.run();
        }
        ranAction = true;
        //            
        nextGeneration();
        return 0;
      } finally {
        //                   
        if (!ranAction) {
          breakBarrier();
        }
      }
    }

    //       0      
    for (;;) {
      try {
        //                      
        if (!timed) {
          trip.await();
        }else if (nanos > 0L) {
          nanos = trip.awaitNanos(nanos);
        }
      } catch (InterruptedException ie) {
        //                        
        if (g == generation && ! g.broken) {
          breakBarrier();
          throw ie;
        } else {
          //                    ,          
          Thread.currentThread().interrupt();
        }
      }
      //                     
      if (g.broken) {
        throw new BrokenBarrierException();
      }
      //                      
      if (g != generation) {
        return index;
      }
      //                        
      if (timed && nanos <= 0L) {
        breakBarrier();
        throw new TimeoutException();
      }
    }
  } finally {
    lock.unlock();
  }
}
위 에 붙 인 코드 중의 주석 은 모두 비교적 상세 하 므 로 우 리 는 중요 한 것 만 골 라 서 말 할 것 이다.dowait 방법 에 서 는 매번 count 를 1 로 줄 이 고 감 소 된 후 바로 0 인지 판단 하 는 것 을 볼 수 있 습 니 다.0 과 같 으 면 이전에 지정 한 임 무 를 먼저 수행 하고 실행 한 후에 nextGeneration 방법 으로 울 타 리 를 다음 세대 로 옮 깁 니 다.이 방법 에 서 는 모든 스 레 드 를 깨 워 카운터 의 값 을 파티 로 재 설정 합 니 다.마지막 으로 울 타 리 를 다시 설치 해 nextGeneration 방법 을 실행 한 뒤 게임 이 다음 판 에 들 어 가 는 것 을 의미한다.계수기 가 0 과 같 지 않 으 면 for 순환 에 들 어가 서 매개 변수 에 따라 trip.awaitNanos(nanos)를 호출 할 지,trip.awat()방법 을 호출 할 지 결정 합 니 다.이 두 가지 방법 은 정시 와 비정 기적 인 대기 에 대응 합 니 다.대기 중 현재 스 레 드 가 중단 되면 breakBarrier 방법 을 실행 합 니 다.이 방법 은 울 타 리 를 깨 는 것 이 라 고 합 니 다.게임 이 중간 에 끊 어 지 는 것 을 의미 합 니 다.generation 의 broken 상 태 를 true 로 설정 하고 모든 스 레 드 를 깨 우 는 것 을 의미 합 니 다.또한 이 는 기다 리 는 과정 에서 하나의 스 레 드 가 전체 게임 이 중단 되면 끝나 고 그 전에 막 힌 스 레 드 가 모두 깨 어 난 다 는 것 을 의미한다.스 레 드 가 깨 어 난 후 다음 세 가지 판단 을 실행 하여 breakBarrier 방법 을 사용 하여 깨 어 났 는 지,만약 그렇다면 이상 을 던 졌 는 지 확인 합 니 다.정상 적 인 세대교체 작업 으로 깨 어 났 는 지,그렇다면 카운터 의 값 을 되 돌려 줍 니 다.시간 초과 로 깨 어 났 는 지,그렇다면 브레이크 배 리 어 를 이용 해 울 타 리 를 깨 고 이상 을 던 져 보 자.여기 서 주의해 야 할 것 은 만약 에 그 중의 한 스 레 드 가 시간 을 초과 해서 종료 되면 전체 게임 도 끝나 고 다른 스 레 드 가 모두 깨 어 날 것 이다.다음은 nextGeneration 방법 과 breakBarrier 방법의 구체 적 인 코드 를 붙 입 니 다.

//        
private void nextGeneration() {
  //          
  trip.signalAll();
  //                
  count = parties;
  //        
  generation = new Generation();
}

//      
private void breakBarrier() {
  //            
  generation.broken = true;
  //                
  count = parties;
  //      
  trip.signalAll();
}
위 에서 우 리 는 이미 소스 코드 를 통 해 Cyclic Barrier 의 원 리 를 기본적으로 명확 하 게 설명 했다.다음은 우 리 는 경마 의 예 를 통 해 그것 의 사용 을 깊이 파악 할 것 이다.

class Horse implements Runnable {
  
  private static int counter = 0;
  private final int id = counter++;
  private int strides = 0;
  private static Random rand = new Random(47);
  private static CyclicBarrier barrier;
  
  public Horse(CyclicBarrier b) { barrier = b; }
  
  @Override
  public void run() {
    try {
      while(!Thread.interrupted()) {
        synchronized(this) {
          //         
          strides += rand.nextInt(3);
        }
        barrier.await();
      }
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
  
  public String tracks() {
    StringBuilder s = new StringBuilder();
    for(int i = 0; i < getStrides(); i++) {
      s.append("*");
    }
    s.append(id);
    return s.toString();
  }
  
  public synchronized int getStrides() { return strides; }
  public String toString() { return "Horse " + id + " "; }
  
}

public class HorseRace implements Runnable {
  
  private static final int FINISH_LINE = 75;
  private static List<Horse> horses = new ArrayList<Horse>();
  private static ExecutorService exec = Executors.newCachedThreadPool();
  
  @Override
  public void run() {
    StringBuilder s = new StringBuilder();
    //      
    for(int i = 0; i < FINISH_LINE; i++) {
      s.append("=");
    }
    System.out.println(s);
    //      
    for(Horse horse : horses) {
      System.out.println(horse.tracks());
    }
    //      
    for(Horse horse : horses) {
      if(horse.getStrides() >= FINISH_LINE) {
        System.out.println(horse + "won!");
        exec.shutdownNow();
        return;
      }
    }
    //           
    try {
      TimeUnit.MILLISECONDS.sleep(200);
    } catch(InterruptedException e) {
      System.out.println("barrier-action sleep interrupted");
    }
  }
  
  public static void main(String[] args) {
    CyclicBarrier barrier = new CyclicBarrier(7, new HorseRace());
    for(int i = 0; i < 7; i++) {
      Horse horse = new Horse(barrier);
      horses.add(horse);
      exec.execute(horse);
    }
  }
  
}
이 경마 프로그램 은 주로 콘 솔 에서 각 경마 의 현재 궤적 을 끊임없이 인쇄 함으로써 동적 디 스 플레이 효 과 를 얻는다.전체 경 기 는 여러 차례 의 라운드 가 있 습 니 다.매 라운드 각 경 마 는 무 작위 로 몇 걸음 걸 은 후에 await 방법 으로 기다 리 고 있 습 니 다.모든 경마 가 한 라운드 가 끝 날 때 임 무 를 수행 하여 모든 경마 의 현재 궤적 을 콘 솔 에 인쇄 합 니 다.이렇게 매 라운드 에서 각 경마 의 궤적 이 끊임없이 증가 하고 있다.그 중에서 어떤 경마 의 궤적 이 가장 먼저 지 정 된 값 으로 증가 할 때 전체 경 기 를 끝 낼 것 이다.이 경 마 는 전체 경기 의 승리자 가 될 것 이다!프로그램의 실행 결 과 는 다음 과 같다.

이로써 우 리 는 Cyclic Barrier 와 Count Downlatch 를 비교 하 는 것 을 피하 기 어렵다.이 두 가지 유형 은 하나의 스 레 드 가 특정한 조건 에 도달 하기 전에 기다 리 는 것 을 실현 할 수 있 습 니 다.내부 에 하나의 카운터 가 있 습 니 다.카운터 의 값 이 0 으로 계속 줄 어 들 면 모든 막 힌 스 레 드 가 깨 어 납 니 다.차이 점 은 Cyclic Barrier 의 계수 기 는 스스로 제어 하고 Count Downlatch 의 계수 기 는 사용자 가 제어 합 니 다.Cyclic Barrier 에서 스 레 드 호출 await 방법 은 자신 을 막 을 뿐만 아니 라 계수 기 를 1 로 줄 일 수 있 습 니 다.Count Downlatch 에서 스 레 드 호출 await 방법 은 자신 을 막 을 뿐 카운터 의 값 을 줄 이지 않 습 니 다.또 Count Downlatch 는 한 바퀴 만 차단 할 수 있 고,Cyclic Barrier 는 순환 차단 이 가능 하 다.일반적으로 Cyclic Barrier 를 사용 하면 Count Downlatch 의 기능 을 실현 할 수 있 지만,반대로 위의 경마 프로그램 은 Cyclic Barrier 만 사용 할 수 있다.한 마디 로 하면 이 두 가지 공통점 은 대체적으로 이와 같다.언제 Cyclic Barrier 를 사용 하 는 지,언제 Count Downlatch 를 사용 하 는 지 에 대해 서 는 독자 스스로 파악 해 야 한다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기