[JAVA:병렬 프로그래밍 - 3] Exchanger 사용하기.
Exchanger 는 뜻 그대로 쓰레드상에서 두 개의 인스턴스를 교환해주는 역할을 한다. 만약 한쪽의 쓰레드에서만 교환 메소드를 호출한다면 다른쪽에서 교환 메소드를 호출하기 전까지 대기하고 있는데.
이 클래스는 java 의 gc 와 같은 동작에서 사용할 수 있다.
예를 들어 설명하면 다음과 같다. (이해를 돕기 위하여 '요청' 이라는 표현을 사용하였다. 정확히는 교환 대상이 되는 인스턴스 값을 인자로 하는 교환 메소드 호출이다.)
1. 데이터를 '스택에 쌓는 쓰레드' 와, '스택을 비우는 쓰레드' 가 있다. 이 두 개의 쓰레드에는 각각의 '스택' 이 있다.
2. 데이터를 '스택에 쌓는 쓰레드' 는 계속해서 데이터를 자신의 '스택' 에 추가한다. 이 동작을 반복하다 스택이 가득차면 '스택' 교환을 요청하고 3번 과정이 끝날 때 까지 모든 동작을 대기한다. 즉, 다른쪽에서도 교환 요청이 일어날 때까지 대기하는 것이다.
3. '스택을 비우는 쓰레드' 에서 스택을 모두 비운다. 스택을 모두 비우면 가득찬 스택을 가져오기 위하여 데이터를 '스택에 쌓는 쓰레드' 의 '스택'과 교환 요청을 한다.
4. 한 쌍의 '스택' 에 대하여 교환 요청이 이루어졌으므로 '스택에 쌓는 쓰레드' 의 가득찬 '스택'은 '스택을 비우는 쓰레드' 로 이동하여 가득찬 '스택'을 비울 수 있도록 한다. 또, '스택을 비우는 쓰레드' 의 비어있는 '스택'은 '스택에 쌓는 쓰레드' 로 이동하여 비어있는 '스택' 에 데이터를 다시 쌓을 수 있도록 한다. 그리고 1번 과정을 반복한다.
이를 예제코드로 표현하면 아래와 같다.
예제코드:
/** * 스택의 최대 크기. */ public final static int STACK_CAPACITY = 8; private Exchanger<LinkedList<Integer>> mExchanger = new Exchanger<LinkedList<Integer>>(); class PushLoop implements Runnable { /** int 값을 넣는데 사용하는 스택. */ LinkedList<Integer> integerStack = new LinkedList<Integer>(); /** 키 입력을 받는다. */ private Scanner keyScanner = new Scanner(System.in); public void run() { while(!Thread.interrupted()) { try { // 스택의 최대 크기를 넘기지 않는 범위내에서 입력받은 값들을 스택에 넣는다. while(integerStack.size() < STACK_CAPACITY) { int value = keyScanner.nextInt(); integerStack.addLast(value); } // 스택이 가득차면 메세지를 출력하고, 비어있는 스택(integerStack)과 교환한다. // 만약 교환될(비어있는) 스택이 없다면 대기한다. System.out.println("PushLoop : Full Stack."); integerStack = mExchanger.exchange(integerStack); } catch (InterruptedException e) { e.printStackTrace(); } } } } class EmptyLoop implements Runnable { /** * 값을 비우는 스택. */ LinkedList<Integer> integerStack = new LinkedList<Integer>(); public void run() { while(!Thread.interrupted()) { try { // 스택내에 값이 비어있지 않으면 계속 비운다. while(!integerStack.isEmpty()) { int value = integerStack.removeLast(); System.out.println(value); } // 스택이 비어지면 메세지를 출력하고, 가득찬 스택(integerStack)과 교환한다. // 만약 교환될(가득찬) 스택이 없다면 대기한다. System.out.println("EmptyLoop : Empty Stack."); integerStack = mExchanger.exchange(integerStack); } catch (InterruptedException e) { e.printStackTrace(); } } } } public void runThread() { new Thread(new PushLoop()).start(); new Thread(new EmptyLoop()).start(); }
위 예제 코드에서 runThread() 를 호출하면 값을 입력 받아서 stack 에 쌓는 쓰레드와 동시에 비우는 쓰레드가 작동한다. 결과는 다음과 같다.
실행 결과(초록색 폰트가 입력된 값이다.):
EmptyLoop : Empty Stack. 100 200 300 400 500 600 700 800 PushLoop : Full Stack. 800 700 600 500 400 300 200 100 EmptyLoop : Empty Stack.