Safe Termination 패턴

패턴 명칭

Safe Termination

필요한 상황

스레드의 종료를 안전하게 하기 위한 패턴이다. 스레드는 기본적으로 stop 매서드를 제공하지만, 이 매서드는 안정성 문제로 deprecated 되었다. 스레드는 자신의 코드가 모두 실행되고 종료되는 것이 가장 이상적이지만, 실행 중간에 종료되어야 할 상황에서 안전하게 종료될 수도 있어야 한다.

예제 코드

먼저 수를 계속 카운팅하는 스레드 Worker를 기동시키는 아래의 코드가 있다.

package tstThread;

public class Main {
	public static void main(String[] args) {
		System.out.println("BEGIN");
		
		Worker worker = new Worker();
		worker.start();
		
		try {
			Thread.sleep(2000);
			
			System.out.println("[stopRequest BEGIN]");
			worker.stopRequest();
			System.out.println("[stopRequest END]");
			worker.join();
		} catch(InterruptedException e) {
			//.
		}
		
		System.out.println("END");
	}
}

Worker 스레드는 다음과 같다.

package tstThread;

public class Worker extends Thread {
	private volatile boolean bStop = false;
	
	public void stopRequest() {
		bStop = true;
		interrupt();
	}
	
	public void run() {
		try {
			for(int i=0; true; i++) {
				if(bStop) break;
				System.out.println("Counting: " + i);
				Thread.sleep(100);
			}
		} catch(InterruptedException e) {
			//.
		} finally {
			release();
		}
	}
	
	private void release() {
		System.out.println("SAFE TERMINATION");
	}
}

bStop 변수가 스레드를 안전하게 종료시키는 장치인데, volatile로 선언되었다. 이 변수는 stopRequest 매서드로 인해 true로 설정되며, interrupt 매서드의 호출을 동반한다. interrupt 매서드의 호출은 wait, sleep로 인해 스레드가 대기하는 상황에서도 안전하게 스레드를 종료시키기 위함이다. 실행 결과는 다음과 같다.

BEGIN
Counting: 0
Counting: 1
Counting: 2
Counting: 3
Counting: 4
Counting: 5
Counting: 6
Counting: 7
Counting: 8
Counting: 9
Counting: 10
Counting: 11
Counting: 12
Counting: 13
Counting: 14
Counting: 15
Counting: 16
Counting: 17
Counting: 18
Counting: 19
[stopRequest BEGIN]
[stopRequest END]
SAFE TERMINATION
END

Future 패턴

패턴 명칭

Future

필요한 상황

스레드는 코드의 실행에 초점이 맞춰져 있고, 그 결과를 받는 시점이 불분명하다. 스레드가 단순히 코드를 실행하는 것에서 끝나는 것이 아니라 그 실행의 결과를 다른 스레드에서 받기 위한 패턴이다.

예제 코드

Proxy 클래스는 어떤 결과를 얻기 위한 코드의 실행을 스레드로 실행해 주는 대리자이다. Proxy는 어떤 코드의 실행을 스레드로 실행해주고 바로 Result 클래스 타입의 객체를 반환한다. 바로 이 Result를 통해 스레드의 결과를 얻을 수 있다. 스레드의 실행 결과를 얻을 수 있는 상태가 되면 Result 객체의 setRealResult를 통해 실제 결과를 담는 RealResult 객체를 Result 클래스의 필드값에 담는다. 추후 적당한 시점에서 Result 클래스의 get 함수를 호출해서 실제 결과를 얻는다. 만약 실제 결과를 얻을 수 없을 때는 실제 결과가 완성될때까지 get 함수는 동기화(Blocking) 된다. 이러한 클래스들을 사용하는 코드는 아래와 같다.

package tstThread;

public class Main {
	public static void main(String[] args) {
		System.out.println("START");
		
		Proxy proxy = new Proxy();
		Result result1 = proxy.run(10, 'A');
		Result result2 = proxy.run(30, 'B');
		Result result3 = proxy.run(20, 'C');
				
		System.out.println("result1 = " + result1.get());
		System.out.println("result2 = " + result2.get());
		System.out.println("result3 = " + result3.get());
		
		System.out.println("END");
	}
}

Proxy 클래스는 다음과 같다.

package tstThread;

public class Proxy {
	public Result run(final int count, final char c) {
		final Result result = new Result();
		
		new Thread() {
			public void run() {
				RealResult realData = new RealResult(count, c);
				result.setRealResult(realData);
			}
		}.start();
		
		return result;
	}
}

Result 클래스는 다음과 같다.

package tstThread;

public class Result {
	private RealResult real = null;
	
	public synchronized void setRealResult(RealResult real) {
		if(this.real != null) {
			return;
		}
		
		this.real = real;
		
		notifyAll();
	}
	
	public synchronized String get() {
		while(real == null) {
			try {
				wait();
			} catch(InterruptedException e) {
				//.
			}
		}
		
		return real.get();
	}
}

RealResult 클래스는 다음과 같다.

package tstThread;

public class RealResult extends Result {
	private final String resultData;
	
	public RealResult(int count, char c) {
		char[] buffer = new char[count];
		for(int i=0; i<count; i++) {
			buffer[i] = c;
			try {
				Thread.sleep(100);
			} catch(InterruptedException e) {
				//.
			}
		}

		this.resultData = new String(buffer);
	}
	
	@Override
	public String get() {
		return resultData;
	}
}

실행 결과는 다음과 같다.

START
result1 = AAAAAAAAAA
result2 = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
result3 = CCCCCCCCCCCCCCCCCCCC
END