패턴 명칭
Worker Thread
필요한 상황
어떤 데이터가 생성되면, 이 데이터를 또 다른 여러 개의 스레드에서 처리한다. 물론 데이터의 생성 역시 또 다른 여러 개의 스레드에서 처리한다. 데이터의 처리를 동시에 처리하면서 처리하는 방식은 한가지로 정해진 것이 아닌 다양한 방식으로 수행되며, 간단이 추가될 수 있어야 한다. 이때 사용할 수 있는 패턴이다. 사실, 데이터의 다양한 방식의 처리는 Worker Thread의 응용이다.
예제 코드
Client 클래스는 처리할 데이터를 생성한다. 이렇게 생성된 데이터는 Request라는 클래스에 담기게 되는데 이 Request는 추상 클래스이며, 이 클래스를 상속받아 데이터에 대한 처리 방식을 정의할 수 있다. Client가 생성한 Request는 바로 처리되는게 아니고 Channel 클래스에 저장된다. 이렇게 저장된 데이터에 대한 Request는 Worker 클래스에의해 스레드로 처리된다. 이 Worker 클래스는 WorkerPool이라는 스레드 저장소에 미리 생성되어 관리된다. 이러한 클래스들을 사용하는 코드는 다음과 같다.
package tstThread; public class Main { public static void main(String[] args) { Channel channel = new Channel(10); WorkerPool workers = new WorkerPool(5, channel); workers.start(); new Client("ClientA", channel).start(); new Client("ClientB", channel).start(); new Client("ClientC", channel).start(); } }
Channel에 최대로 저장할 수 있는 Request의 개수는 10개, 데이터를 처리하는 Worker의 개수는 5개로 정했으며, 데이터를 생성하는 Client 스레드의 개수는 3개이다. Client 클래스의 코드는 다음과 같다.
package tstThread; import java.util.Random; public class Client extends Thread { private final Channel channel; private static final Random random = new Random(); public Client(String name, Channel channel) { super(name); this.channel = channel; } public void run() { try { for(int i = 0; true; i++) { Request request; Thread.sleep(random.nextInt(1000)); if(random.nextInt(2) == 0) { request = new OneRequest(getName(), i); } else { request = new TwoRequest(getName(), i); } channel.putRequest(request); } } catch(InterruptedException e) { //. } } }
처리해야할 데이터는 정수값이며, 이 정수값의 데이터에 대한 처리는 무작위로 결정되는데, 실제 처리에 대한 코드는 OneRequest와 TwoRequest 클래스에 정의되어 있다. Request 추상 클래스에 대한 코드는 다음과 같다.
package tstThread; public abstract class Request { protected final String clentName; protected final int number; public Request(String clentName, int number) { this.clentName = clentName; this.number = number; } public abstract void execute(); }
이 추상클래스를 구현하는 OneRequest 클래스는 다음과 같다.
package tstThread; public class OneRequest extends Request { public OneRequest(String name, int number) { super(name, number); } public void execute() { String result = "ECHO: " + number + "@" + clentName; System.out.println(Thread.currentThread().getName() + " -> " + result); } }
또 다른 처리 방식인 TwoRequest 클래스는 다음과 같다.
package tstThread; public class TwoRequest extends Request { public TwoRequest(String name, int number) { super(name, number); } public void execute() { String result = "POWER: " + (number*number) + "@" + clentName; System.out.println(Thread.currentThread().getName() + " -> " + result); } }
이 Request 클래스에 대한 객체는 Client가 생성하여 Channel에 저장되는데, Channel 클래스의 코드는 다음과 같다.
package tstThread; import java.util.LinkedList; public class Channel { private final int maxCountRequests; private final LinkedList<Request> requestQueue = new LinkedList<Request>(); public Channel(int maxCountRequests) { this.maxCountRequests = maxCountRequests; } public synchronized void putRequest(Request request) { while(requestQueue.size() >= maxCountRequests) { try { wait(); } catch(InterruptedException e) { //. } } requestQueue.addLast(request); notifyAll(); } public synchronized Request takeRequest() { while(requestQueue.size() <= 0) { try { wait(); } catch(InterruptedException e) { //. } } Request request = requestQueue.pollFirst(); notifyAll(); return request; } }
데이터를 처리하는 Worker 클래스는 다음과 같다.
package tstThread; public class Worker extends Thread { private final Channel channel; public Worker(String name, Channel channel) { super(name); this.channel = channel; } public void run() { while(true) { Request request = channel.takeRequest(); request.execute(); } } }
Worker 클래스의 객체는 WorkerPool이라는 클래스를 통해 생성되어 관리되며 코드는 다음과 같다.
package tstThread; public class WorkerPool { private final Worker[] threadPool; public WorkerPool(int countThreads, Channel channel) { threadPool = new Worker[countThreads]; for(int i=0; i<threadPool.length; i++) { threadPool[i] = new Worker("Worker-" + i, channel); } } public void start() { for(int i=0; i<threadPool.length; i++) { threadPool[i].start(); } } }
실행 결과는 다음과 같다.
Worker-0 -> ECHO: 0@ClientA Worker-4 -> ECHO: 0@ClientB Worker-3 -> POWER: 0@ClientC Worker-2 -> POWER: 1@ClientB Worker-3 -> POWER: 4@ClientB Worker-2 -> POWER: 1@ClientC . . . orker-0 -> POWER: 625@ClientC Worker-2 -> POWER: 784@ClientB Worker-0 -> POWER: 841@ClientB Worker-2 -> POWER: 784@ClientA Worker-0 -> POWER: 841@ClientA Worker-2 -> ECHO: 30@ClientB Worker-0 -> POWER: 676@ClientC Worker-2 -> ECHO: 30@ClientA Worker-3 -> POWER: 961@ClientA Worker-2 -> ECHO: 32@ClientA Worker-3 -> POWER: 961@ClientB