Myo-Kyeong Tech Blog

[ JAVA ] 스레드 풀(Thread Pool) - Java로 Thread Pool의 구현 및 서버 애플리케이션 활용 본문

Programming/Java

[ JAVA ] 스레드 풀(Thread Pool) - Java로 Thread Pool의 구현 및 서버 애플리케이션 활용

myo-kyeong 2023. 7. 16. 18:07
728x90
반응형

 

2023.07.16 - [Programming/Java] - [ JAVA ] 스레드 풀(Thread Pool) - Thread Pool을 사용해야 하는 이유

 

[ JAVA ] 스레드 풀(Thread Pool) - Thread Pool을 사용해야 하는 이유

Thread-per-Request 모델이란? 서버 요청이 있을 때마다 새로운 스레드를 생성하고 처리하는 방식 request : Thread = 1 : 1 로 매핑되어서 하나의 request를 하나의 Thread가 처리하는 방식 Thread-per-Request 모델

data04190.tistory.com

 

이전 글에서 Thread Pool이 왜 필요한지에 대해 설명했었는데요. 이번 글에서는 그 이유를 바탕으로 직접 Java 코드로 Thread Pool을 구현하고, 이를 활용하여 서버 애플리케이션을 만들어 보는 과정에 대해 살펴보겠습니다. 

 


 

Thread Pool 구현하기 

우선, Thread Pool과 각각의 스레드가 어떻게 동작해야 하는지에 대해 이해해야 합니다. 각각의 스레드는 요청이 들어올 때마다 생성되지 않고, Thread Pool에 의해 관리되며 재사용됩니다. 이를 위해 ManagedThread라는 이름의 클래스를 만들어, 각각의 스레드가 수행할 작업(Job)을 지정하고, 작업이 완료되면 Thread Pool로 스레드를 반환하는 기능을 구현합니다. 

이러한 스레드 관리를 위해 Thread Pool을 구현할 때는, 처음에 스레드를 생성하고, 사용 후에는 반환하는 기능이 필요합니다. 이를 위해 'ResourcePool'라는 인터페이스를 정의하고, 이를 구현한 'ThreadPool' 클래스를 만들어서 사용합니다. 

 

[ 클래스 및 인터페이스 정의 ]

  • ManagedThread : 실행할 작업(Job)을 가지고 있는 스레드를 나타내는 클래스. 작업이 할당되면 스레드는 해당 작업을 수행
  • Job : 수행할 작업을 나타내는 인터페이스
  • ResourcePool : 스레드를 관리하는 풀의 인터페이스
  • ThreadPool : ResourcePool 인터페이스를 구현하는 클래스로, 실제로 스레드를 생성, 재사용, 반환하는 역할

 

ManagedThread

ManagedThread는 ResourcePool의 리소스를 참조하며, 실제 작업(Job)을 수행하는 역할을 합니다. 작업(Job)이 설정되면, 이 작업을 수행한 후 다시 ThreadPool로 반환됩니다.

public class ManagedThread extends Thread {
  static int count = 0;

  int no;
  ResourcePool<ManagedThread> pool;
  Job job;

  public ManagedThread(ResourcePool<ManagedThread> pool) {
    this.pool = pool;
    no = ++count;
  }

  public void setJob(Job job) {
    this.job = job;
    synchronized (this) {
      this.notify();
    }
  }

  @Override
  synchronized public void run() {
    while (true) {
      try {
        synchronized (this) {
          this.wait();
        }
        System.out.printf("%d 번 스레드 실행!\n", no);
        this.job.execute();
        pool.returnResource(this);

      } catch (Exception e) {
        System.err.println("스레드 실행 중 오류 발생!");
        e.printStackTrace();
      }
    }
  }
}

 

Job 인터페이스

Job 인터페이스는 execute()라는 단일 메소드를 가지고 있습니다. 이 메소드를 통해 작업(Job)이 수행되며, 이를 ManagedThread에서 호출합니다.

public interface Job {
  void execute();
}

 

ResourcePool 인터페이스

ResourcePool 인터페이스는 getResource()와 returnResource()라는 두 개의 메소드를 가집니다. 이 인터페이스를 통해 스레드 풀의 리소스를 관리할 수 있습니다.

public interface ResourcePool<T> {
  T getResource();
  void returnResource(T resource);
}

 

ThreadPool

마지막으로, ThreadPool 클래스는 위에서 설명한 ResourcePool 인터페이스를 구현합니다. 이 클래스는 스레드를 생성하고, 필요한 경우 기존 스레드를 재사용하며, 사용이 끝난 스레드를 반환합니다.

public class ThreadPool implements ResourcePool<ManagedThread>{

  ArrayList<ManagedThread> list = new ArrayList<>();

  @Override
  public ManagedThread getResource() {
    ManagedThread t = null;

    if (list.size() == 0) {
      t = new ManagedThread(this);
      System.out.printf("새 스레드 생성: %d\n", t.no);
      t.start();

      try {
        Thread.sleep(100);
      } catch (Exception e) {}

      return t;
    }

    t = list.remove(0);
    System.out.printf("기존 스레드 리턴: %d\n", t.no);
    return t;
  }

  @Override
  public void returnResource(ManagedThread resource) {
    list.add(resource);
    System.out.printf("스레드 반납: %d\n", resource.no);
  }
}

 

Thread Pool을 활용한 서버 애플리케이션 구현

  • ServerApp : 클라이언트의 요청을 받아 해당 요청을 처리하는 스레드를 생성하거나 재사용

 

ServerApp

ServerApp은 소켓을 통해 클라이언트의 요청을 받아들입니다. 그런 다음, 각 요청에 대해 새로운 ManagedThread를 만들거나 기존의 하나를 재사용하여 요청을 처리합니다. 이 과정에서 이전에 만들었던 ThreadPool이 활용됩니다.

public class ServerApp {
  public static void main(String[] args) {
    // Set up the thread pool
    ThreadPool threadPool = new ThreadPool();

    try (ServerSocket serverSocket = new ServerSocket(8888)) {
      while (true) {
        Socket socket = serverSocket.accept();
        
        // Get a thread from the pool
        ManagedThread t = threadPool.getResource();
        
        // Set the job for the thread
        t.setJob(() -> processRequest(socket));
      }
    } catch (IOException e) {
      System.err.println("서버 준비 중 오류 발생!");
      e.printStackTrace();
    }
  }

  public static void processRequest(Socket socket) {
    // Insert request processing code here...
  }
}

 

processRequest()

processRequest() 메소드는 클라이언트의 요청을 처리하는 역할을 합니다. 이 메소드 내에서 클라이언트의 요청을 받아들이고, 적절한 응답을 전달하는 로직을 작성합니다.

public void processRequest(Socket socket) {
    try (Socket s = socket;
        DataInputStream in = new DataInputStream(socket.getInputStream());
        DataOutputStream out = new DataOutputStream(socket.getOutputStream())) {
      // Processing request logic goes here...
    } catch (Exception e) {
      System.out.println(e.getMessage());
    }
}
728x90
반응형