티스토리 뷰

donaricano-btn
반응형

컨테이너는 클라이언트로부터 요청이 있을 때 마다 HttpServletRequest와 HttpServletResponse 객체를 생성하고 서블릿 인스턴스를 찾아 실행시킵니다. 그런데 클라이언트의 요청은 순차적으로 이어지지 않을 확률이 높습니다. 서비스의 규모가 크다면 수 많은 요청이 한 번에 일어나게 되겠죠.

 

그래서 컨테이너는 요청이 들어올 때 마다 새로운 스레드를 생성하거나 스레드풀에서 스레드를 가져와서 요청을 처리하게 됩니다. 서블릿의 동작구조는 지난 포스트를 참조 부탁드립니다.

 

스레드는 사용자에게 한 번에 여러가지 일을 처리하는 것 처럼 보이게 만들지만 실제로는 아주 빠른 속도로 스레드를 오가며 작업을 처리하는 것에 불과합니다. CPU는 한 번에 한 가지의 작업만 처리할 수 있기 때문입니다. 그래서 기본적으로 생성된 스레드는 위 그림과 같은 생명주기를 가지게 됩니다.

 

스레드의 생명주기와 실행제어에 관한 자세한 설명은 지난 포스트를 참조 바랍니다.

 

스레드 풀을 구성하는 방법은 여러가지가 있겠지만 기본적으로 queue 자료구조를 이용하는 경우가 많습니다. 저는 이 프로젝트에서 queue에 선입선출 방식으로 task를 처리하는 아주 간단한 스레드 풀을 만들도록 하겠습니다. 

 

public class FixedThreadPool {

    private final int numberOfThread;
    private final Queue<Runnable> taskQueue;
    private final ThreadExcecutor[] threads;

    public FixedThreadPool(int numberOfThread) {
        this.numberOfThread = numberOfThread;
        taskQueue = new LinkedList<Runnable>();
        threads = new ThreadExcecutor[numberOfThread];
        init();
    }

    private void init() {
        for (int i = 0; i < numberOfThread; i++) {
            threads[i] = new ThreadExcecutor(taskQueue);
            threads[i].start();
        }
    }

    public void execute(Runnable task) {
        synchronized (taskQueue) {
            taskQueue.offer(task);
            taskQueue.notifyAll();
        }
    }
}

 

제가 구현한 것은 스레드의 개수가 정해진 FixedThreadPool 입니다.  먼저 실행할 작업이 저장될 taskQueue를 생성하고 생성할 스레드의 개수많큼 스레드를 생성합니다. 그리고 execute 메소드를 통해 새로운 작업을 taskQueue에 추가하게 됩니다.

 

public class ThreadExcecutor extends Thread {

    final private Queue<Runnable> taskQueue;

    public ThreadExcecutor(Queue<Runnable> taskQueue) {
        this.taskQueue = taskQueue;
    }

    @Override
    public void run() {
        while (true) {
            Runnable task = null;
            synchronized (taskQueue) {
                while (taskQueue.isEmpty()) {
                    try {
                        taskQueue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                task = taskQueue.poll();
            }
            try {
                task.run();
            } catch (RuntimeException e) {
                e.printStackTrace();
            }
        }
    }
}

 

ThreadExcecutor 클래스는 할당받은 작업을 실제로 수행하는 클래스 입니다. 이 클래스는 Thread를 상속받고 있습니다. 만약 실행해야하는 작업이 없다면, 즉 taskQueue가 비어있는 상태라면 수행을 멈추고 새로운 작업이 추가될 때 까지 기다리는 로직을 가지고 있습니다.

 

이 taskQueue를 사용하도록 기존의 코드를 고쳐 보겠습니다.

 

public class RequestHandler implements Runnable {

    private static final String APP_ROOT = "../../app";

    private Socket socket;
    private ServletMap servletMap;

    public RequestHandler(Socket socket, ServletMap servletMap) {
        this.socket = socket;
        this.servletMap = servletMap;
    }

    @Override
    public void run() {
        try {
            HTTPServletRequest request = new HTTPServletRequest();
            RequestReader requestReader = new RequestReader(request);
            requestReader.run(socket.getInputStream());

            String url = request.getRequestUrl();
            File file = new File(APP_ROOT + url);
            if (file.isFile()) {
                HTTPServletResponse response = new HTTPServletResponse(file, socket.getOutputStream());
                response.setFileContentType();
                response.sendFile();
                return;
            }
            if (servletMap.isServlet(url)) {
                HTTPServletResponse response = new HTTPServletResponse(socket.getOutputStream());
                servletMap.loadClass(url);
                Servlet servlet = servletMap.getInstance(url);
                servlet.init();
                servlet.service(request, response);
                servlet.destroy();
                return;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

기존에 요청을 처리하는 코드를 Runnable을 구현한 RequestHandler 클래스로 분리했습니다. 

 

    public void run() {
        try {
            init();

            ServerSocket listener = new ServerSocket(PORT_NUMBER);
            FixedThreadPool threadPool = new FixedThreadPool(NUMBER_OF_THREAD);
            while (true) {
                threadPool.execute(new RequestHandler(listener.accept(), servletMap));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

 

그리고 컨테이너에서는 요청이 있을 때 마다 threadPool 에 이 requestHandler 를 추가합니다.

 

이렇게 컨테이너에서 스레드풀을 사용해 여러 요청을 처리하는 것 까지 완성해 보았습니다.

반응형
donaricano-btn
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함