make it simple
article thumbnail
Published 2023. 1. 22. 22:23
[JAVA] Process와 Thread - 일해라 일! Java

개요

  • 몇 천개의 url을 검사하는 시스템을 개발해야 하는 일이 생겼다. 간단하게 scheduler를 사용해서 RestTemplate으로 통신 후 StatusCode에 따라 검수하는 시스템을 만들었다. 로컬에서 테스트한 결과 대략 40분이 걸렸다. 시간이 너무 오래걸린다 느껴 url를 10개 list로 나눠서 multi-threading 으로 진행해서 시간을 15분으로 단축시켰다.  하지만 multi-thread로 구현하면서 부족함을 느껴 process/thread를 공부하고 글을 작성하게 되었다.

Process

  • OS로 부터 자원(메모리) 를 받아 실행하는 프로그램이다. 실질적으로 작업을 수행하는 것이 Thread 이다.
  • 모든 Process는 최소한 하나 이상의 Thread가 존재하면, 둘 이상의 Thread를 가진 Process를 Multi-Threaded Process 라고한다.

Multi-Thread

  • Multi-Thread활용된 사례
    • 메신저로 채팅하면서 파일을 다운받으며 음성대화를 나눌 수 있는 이유가 Multi-Threading을 사용해서 여러개의 일을 할 수가 있다. 만약, Single-Thread로 작성을 했다면 채팅을 하다가 파일을 다운받으려면 멈추고 파일을 다운하고 다시 채팅을 해야한다.
    • 여러 사용자에게 서비스를 해주려면 Multi-Thread로 작성하는게 필수적이다. 그래야 하나의 서버 프로세스가 여러개의 Thread를 생성해 사용자의 요청이 일대일로 처리되서 스무스하게 진행된다.
  • 장점
    • CPU의 사용률을 향상시킨다.
    • 자원을 보다 효율적으로 사용할 수 있다.
    • 사용자에 대한 응답성이 향상된다.
    • 작업이 분리되어 코드가 간결해진다.
  • 단점
    • 여러 Thread가 같은 Process에서 자원을 공유해 동기화(synchronization) 문제가 발생될 수 있다.
    • 교착상태(deadlock: 두 쓰레드가 자원을 점유한 상태에서 서로 상대편이 점유한 자원을 사용하려고 기다리느라 진행이 멈춰있는 상태)이 발생될 수 있다.
  • 결론
    • Single-Thread로 프로그램을 작성하면 사용자의 요청 마다 새로운 프로세스를 생성하고 매 프로세스마다 메모리 공간과 생성되는 시간이 소모되 많은 수의 사용자 요청을 처리하기 어렵다. 그래서 이 때 Multi-Thread를 사용하면 시간과 메모리가 절약되고 더 빠른 서비스가 제공된다. 물론 단점(밑에서 설명)도 있다. 그래서, 적절하게 상황에 맞게 고려하고 고려해서 Multi-Thread로 진행할지 Single-Thread로 진행할지 고려해봐야한다.

Thread 구현 방법

  • 방법 1: Thread클래스를 상속
class MyThread extends Thread {
    public void run () {
    } // Thread클래스의 run ()을 오버라이딩
}
public static void main(String[] args) {
		Thread thread = new MyThread();
	}

Thread를 상속받으면 위와 같이 Thread 객체를 생성할 수 있다.

  • 방법 2: Runnable 인터페이스를 구현
class MyThread implements Runnable {
     public void run() { 
     } // Runnable인터페이스의 run()을구현
}
public static void main(String[] args) {
   Thread thread = new Thread(new myThread());
}

Runnable 인터페이스를 구현하면 위와 같이 Thread 객체 안에 구현한 Runnable을 넘기면 Thread 객체를 생성할 수 있다. 그 이유는 밑처럼 Thread 클래스 내부를 살펴보면 생성자를 통해서 Runnable객체를 생성자에 넘겨서 생성하게 되어있어서 Runnable인터페이스를 구현 후 new Thread()안에 인자로 구현한 객체를 넘겨서 사용해야한다.

public class Thread {
    private Runnable r; // Runnable을 구현한클래스의 인스턴스를 참조하기 위한 변수 
    
    public Thread(Runnable r) {
    	this.r = r;
    }
    
    public void run () { 
        if(r! = null)
             r. run () ; // Runnable인터페이스를 구현한 인스턴스의 run ()을 호출
    } 
    ...
 }

일반적으로 Runnable인터페이스를 구현하는 방법이 재사용성이 높고 코드의 일관성을 유지할 수 있기 때문에 객체지향적인 방법이라 할 수 있다. Thread 클래스에서 특정 함수를 override 하지 않는 이상 인터페이스를 구현하는게 좋다. 상속(extends)은 한 조상클래스밖에 안되기 때문에 다형성을 활용하기 위해선 Runnable 인터페이스를 구현하는게 좋다. 사실상, 두 방법 다 run()함수의 몸통을 채워넣기 위해 해야하는 일이라 위 두가지 방법 중 어떤 방법을 사용해서 Thread를 구현해도 상관없다. 


Thread 실행시키기

public class MyThread1 extends Thread {
    public void run () {
        for(int i=0;i < 25; i++){
            System.out.println("Thread 상속");
        }
    } // Thread클래스의 run ()을 오버라이딩
}

 

public class MyThread2 implements Runnable{
    public void run() {
        for(int i=0;i < 25;i++){
            System.out.println("Runnable 구현");
        }
    }
}
public static void main(String[] args) {
   Thread thread1 = new MyThread1();
   Thread thread2 = new Thread(new MyThread2());

   thread1.start();  // 상속 (extends)
   thread2.start();  // 인터페이스 구현(implements) 
}

 

  • Thread를 생성하면 실행이 바로 되지않는다. 위처럼 Thread객체를 생성하고 start() 를 호출해야 Thread가 실행된다.
  • start()가 호출되었다고 바로 실행되지 않는다, 실행 대기 상태 -> 자신의 차례가되야 실행이 된다. (OS의 스케줄러가 작성한 스케줄에의해 실행 순서가 결정)
ThreadExl_l tl = new ThreadExl_l();
tl.start();
tl.start();
  • Thread는 한번 실행되면 다시 실행이 불가한다. 다시 실행할 경우 IllegalThreadStateException이 발생한다. 

Thread 2개를 생성 후 실행 시키면 위처럼 결과가 나온다. 순서가 보장되지않고 출력이 된다. 위처럼 나오면 Thread가 잘 실행되었다.


결론

  • Process는 실행하는 프로그램, Thread는 그 안에 실질적으로 작업을 수행하는 것이다. 개인적으로 필자는 multi-threading을 개발하면서 자주 쓰는거 같지는 않다. 하지만, 분명 개발할 때 한 프로세스안에 동시적인 일을 실행시킬일이 있을 때 multi-threading을 사용한다. Thread를 사용하기 위해서는 적절히 알아야 상황에 맞게 사용할 수 있는 거 같다. 아니면 오히려 독(병목현상, 동시성 문제)이 생길 수 있다. 간단하게 thread는 무엇인가? 와 어떻게 thread를 실행시키나? 를 알아봤다. 다음 글에는 thread 병목현상/동시성 문제 관련해서 글을 작성해야겠다.
profile

make it simple

@keep it simple

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!