Java에서 request mapping은 Spring Framework에서 제공하는 기능 중 하나로, 클라이언트의 요청에 따라 적절한 메소드를 호출하고 그 결과를 반환하는 것을 말합니다. 이때, 멀티스레드를 이용하면 여러 클라이언트의 요청을 동시에 처리할 수 있습니다.

위 주제의 고민은 서버스펙 및 물리적 인프라 스팩을 적절하게 프로비저닝하여 소프트웨어의 성능을 유지 및 햫상시킬 수 있으나, 소프트웨어 로직 측면에서도 성능을 개선할 수 있는 포인트가 있지 않을까 하는 의문에서 시작되었습니다.

JAVA에서 멀티스레드 구현하기

다음은 Spring Framework에서 Request Mapping을 이용한 멀티스레드 처리를 위한 예시 코드입니다. 이 코드는 클라이언트로부터 요청이 들어오면, 각 요청에 대해 별도의 스레드를 생성하고, 해당 스레드에서 요청을 처리하는 방식으로 동작합니다.

@Controller
public class RequestController {

    @Autowired
    private ExecutorService executorService;

    @RequestMapping("/processRequest")
    @ResponseBody
    public String processRequest() {
        executorService.submit(new RequestHandler());
        return "Request processing has started.";
    }

    private class RequestHandler implements Runnable {
        @Override
        public void run() {
            // 요청 처리 로직 작성
        }
    }
}

위 코드에서 processRequest 메소드는 클라이언트로부터 요청이 들어오면 실행되며, **executorService**를 이용하여 새로운 스레드를 생성하고, 해당 스레드에서 RequestHandler 클래스의 run 메소드를 실행합니다. RequestHandler 클래스는 Runnable 인터페이스를 구현하여, 별도의 스레드에서 실행될 수 있는 클래스입니다. 따라서, 각 요청에 대해 별도의 스레드가 생성되고, 해당 스레드에서 요청 처리 로직이 실행됩니다.

위 코드에서 **executorService**는 스레드 풀을 구현한 객체로, submit 메소드를 이용하여 새로운 스레드를 생성하고, 해당 스레드에서 실행될 작업을 지정할 수 있습니다. 이를 통해, 스레드 생성 및 관리를 쉽게 할 수 있습니다.

Session을 체크하여 선행스레드 Lock

다음은 특정 사용자의 세션을 체크해서 현재 실행중인 스레드가 있다면 별도의 스레드를 만들지 않고 앞의 스레드를 처리되기까지 기다렸다가 스레드를 생성하는 코드 예시입니다.

@Controller
public class RequestController {

    private final Object lock = new Object();

    private Map<String, Thread> threadMap = new ConcurrentHashMap<>();

    @RequestMapping("/processRequest")
    @ResponseBody
    public String processRequest(HttpServletRequest request) {
        String sessionId = request.getSession().getId();

        synchronized (lock) {
            if (threadMap.containsKey(sessionId)) {
                Thread thread = threadMap.get(sessionId);
                if (thread.isAlive()) {
                    try {
                        thread.join();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
            }

            Thread newThread = new Thread(new RequestHandler(sessionId));
            threadMap.put(sessionId, newThread);
            newThread.start();
        }

        return "Request processing has started.";
    }

    private class RequestHandler implements Runnable {

        private final String sessionId;

        public RequestHandler(String sessionId) {
            this.sessionId = sessionId;
        }

        @Override
        public void run() {
            // 요청 처리 로직 작성
            synchronized (lock) {
                threadMap.remove(sessionId);
            }
        }
    }
}

위 코드에서, processRequest 메소드는 클라이언트로부터 요청이 들어오면, 현재 실행 중인 스레드가 있는지 확인하고, 있다면 해당 스레드가 종료될 때까지 기다린 후 새로운 스레드를 생성하여 요청 처리를 진행합니다.

**threadMap**은 각 세션에 대응하는 스레드 객체를 저장하기 위한 자료구조로, **ConcurrentHashMap**을 이용하여 스레드 안전성을 보장합니다. **lock**은 동기화를 위한 객체로, 여러 스레드가 동시에 **threadMap**에 접근하여 수정하는 것을 방지하기 위해 사용됩니다.

RequestHandler 클래스는 각 요청에 대해 별도의 스레드에서 실행될 클래스로, 생성자에서는 현재 요청에 대한 세션 ID를 받아 저장합니다. run 메소드에서는 요청 처리 로직을 작성하며, 처리가 완료되면 **threadMap**에서 해당 세션 ID에 대응하는 스레드 객체를 제거합니다.