티스토리 뷰

Language/JAVA

[JAVA]Thread란?

retto9522 2024. 9. 9. 15:10

프로세스와 쓰레드

프로세스

시스템에서 실행 중인 프로그램을 이야기합니다. 

자신만의 메모리 공간을 포함한 독립적인 실행 환경을 가지고 있습니다. 우리가 사용하는 프로그램들의 일부는 여러 프로세스 간 상호 작용하는 것일 수 있습니다.

JVM은 하나의 프로세스로 실행되며, 동시에 여러 작업을 수행하기 위해 멀티 쓰레드를 지원하고 있습니다.

 

쓰레드

프로세스 안에서 독립적으로 실행되는 작은 실행 단위를 의미합니다.

쓰레드는 JVM에 의해 관리되며, 프로세스에는 적어도 한 개 이상의 쓰레드가 존재합니다. Main 쓰레드 하나로 시작하여 쓰레드를 추가 생성하게 되면 멀티 쓰레드 환경이 됩니다.

이러한 쓰레드는 프로세스의 리소스를 공유하기 때문에 효율적이긴 하나, 리소스 동기화와 같은 문제점이 발생합니다.


JAVA의 쓰레드

Java에서는 멀티 쓰레드를 지원하여 하나의 프로세스 안에 한 개 이상의 쓰레드를 지원하는 구조를 띄고 있습니다.

Java에서는 이러한 멀티 쓰레드를 통해서 비동기식 병렬 어플리케이션을 개발할 수 있습니다.

비동기 식 및 병렬 어플리케이션?
어플리케이션이 실행 중인 동안 사용자가 다른 작업을 수행할 수 있도록 합니다. 웹 브라우저에서는 웹 페이지를 로드하는 동안 사용자가 다른 작업을 수행할 수 있습니다.

예를 들면, 이메일을 보내는 어플리케이션은 이메일을 보내는 작업과 동시에 사용자의 이메일을 읽는 작업을 수행할 수 있습니다.
Blocking과 Non-blocking
- Blocking : I/O가 동작되고 있는 동안 다른 일을 처리하지 못하는 상태를 의미합니다. 함수가 모든일을 마무리하기 전까지 다음 처리가 안되는 것을 의미합니다.

- Non-Blocking : I/O가 동작하면서 request를 받으면 바로 다음 처리에 요청을 보내놓고 다른 작업을 처리하다가 먼저 요청한 작업이 끝나면 이벤트를 받아서 응답을 보내는 것을 의미합니다.

쓰레드의 구조

단일 쓰레드

단일 쓰레드의 구조

 

멀티 쓰레드

멀티 쓰레드의 구조

 

Code

  • 프로그램의 코드가 저장되는 영역입니다.
  • Thread들은 같은 코드 영역을 공유하며, 이 영역은 읽기 전용입니다. 따라서 Thread 간에는 이 영역에 대한 동기화가 필요하지 않습니다.

Data

  • 전역 변수와 정적 변수가 저장되는 영역입니다. 해당 영역은 프로그램이 실행되고 종료될 때 까지 메모리에 존재하는 영역입니다.
  • Thread들은 같은 데이터 영역을 공유하며, 이 영역도 읽기 전용 이므로 Thread 간에 데이터 영역에 대한 동기화가 필요하지 않습니다. 하지만, 여러 Thread가 동시에 사용하는 경우에 동기화 문제가 발생할 수 있습니다. 

Heap

  • 동적 할당된 메모리가 저장되는 영역을 의미합니다.
  • Thread 간 Heap 영역을 공유합니다. 이 영역은 여러 Thread가 동시에 쓰는 경우 동기화 문제가 발생할 수 있기 때문에 적절한 동기화 기법을 사용해야 합니다.

Stack

  • 함수 호출 시 생성되는 지역변수, 매개변수가 임시 저장됩니다.
  • 다만, 함수 호출이 끝나면 해당 영역의 메모리에서 해제가 되는 영역을 의미합니다.
Thread 동기화 문제?
Thread는 프로그램이 동시에 여러 작업을 할 수 있도록 해주는 기능입니다. 하지만, 여러 Thread가 동시에 하나의 자원에 접근하면서 발생하는 동기화 문제입니다.

Thread는 실행되는 시간이 OS에 의해 관리되기 때문에 어떤 쓰레드가 먼저 실행될지 예측할 수 없습니다. 따라서 여러 쓰레드가 동시에 하나의 자원에 접근하게 되면, 결과를 예측할 수 없게됩니다. 또한, 쓰레드가 자원에 접근하는 순서에 따라서 원하는 결과를 얻을 수 없는 경우도 있습니다.

단일 쓰레드 VS 멀티 쓰레드

 

단일 쓰레드

  • 하나의 프로세스에 하나의 쓰레드만 실행되는 것을 의미합니다.(순차 실행)
    프로그램이 하나의 작업만 수행할 수 있다는 것이며, 다른 작업이 실행되기 전에 현재 작업이 완료 되어야 합니다.
  • 멀티 태스킹을 지원하지 않기 때문에 처리량이 낮아지는 단점이 있습니다.

멀티 쓰레드

  • 하나의 프로세스 내에서 동시에 여러 개의 쓰레드가 실행되는 것을 말합니다.(병행 실행)
    쓰레드가 동시에 여러 작업을 처리하기 때문에 시스템의 성능을 향상할 수 있습니다.
  • 프로그램의 작업을 분할하여 처리하기 때문에 여러 작업을 동시에 처리하여 빠르고 효율적입니다.
  • 하지만, 쓰레드 간의 동기화 같은 부작용으로 이를 고려해야 합니다.

단일 쓰레드와 멀티 쓰레드의 차이 

단일 쓰레드와 멀티 쓰레드의 차이

 

단일 쓰레드보단 멀티 쓰레드?

  • 멀티 쓰레드는 한 번에 여러 작업을 처리할 수 있지만, 쓰레드 간의 경쟁으로 인해 자원 공유 및 동기화 문제가 발생할 수 있습니다. 이를 올바르게 처리하지 않으면 예측할 수 없는 결과를 초래하게 됩니다.
  • 반면, 단일 쓰레드는 하나의 작업을 처리하고 다음 작업으로 이동합니다. 이는 자원 공유와 동기화 문제를 방지할 수 있습니다. 또한, 단일 쓰레드의 실행 순서는 예측 가능합니다.
  • 따라서, 쓰레드 별 장단점이 존재하며, 상황에 따라 선택해야 합니다.

쓰레드 상태

쓰레드 상태의 종류

열거 상수 상태 설명
Thread.State.NEW 객체 상태 쓰레드가 생성되었지만 아직 시작되지 않은 상태
Thread.State.RUNNABLE 실행 대기 쓰레드가 실행 가능한 상태
Thread.State.BLOCKED 일시 정지 쓰레드가 일시적으로 중단된 상태
Thread.State.WAITING 일시 정지 쓰레드가 다른 쓰레드의 특정 작업 완료를 기다리는 상태
Thread.State.TIMED_WAITING 일시 정지 쓰레드가 일정 시간 동안 기다리는 상태
Thread.State.TERMINATED 종료 쓰레드가 실행을 완료한 상태

 

쓰레드의 동작 프로세스

  1. Thread는 NEW 라는 쓰레드가 생성됩니다.
  2. Thread는 RUNNABLE 이라는 실행 가능한 상태로 변경됩니다.
  3. Thread는 RUNNING의 상태로 실행됩니다.
  4. 이후, Thread는 각각의 상태가 될 수 있습니다.
    • BLOCKED
    • WAITING
    • TIMED_WAITING
    • TERMINATED
  5. WAITING 상태라면, 다른 쓰레드의 작업 완료를 기다리는 상태로 Thread B가 수행될 때를 기다립니다.
  6. 각각의 상태에서 다시 RUNNABLE 상태로 대기 상태가 됩니다.

쓰레드의 메서드

메서드 설명
start() 쓰레드를 시작합니다.
run() 쓰레드의 작업을 정의합니다.
sleep(long millis) 현재 쓰레드를 주어진 시간(밀리초)만큼 일시 중지합니다.
yield() 현재 쓰레드를 일시 중지하고 다른 쓰레드에게 양보합니다.
join() 다른 쓰레드가 종료될 때 까지 현재 쓰레드를 일시 중지합니다.
isAlive() 쓰레드가 실행 중인지 여부를 반환합니다.
setName(String name) 쓰레드의 이름을 지정합니다.
getName() 쓰레드의 이름을 반환합니다.
setPriority(int priority) 쓰레드의 우선 순위를 지정합니다.
getPriority() 쓰레드의 우선 순위를 반환합니다.
interrupt() 쓰레드의 작업을 중단시킵니다.

 

쓰레드 관련 Object 메서드

메서드 설명
wait() 현재 쓰레드를 일시적으로 중지시키고, 다른 쓰레드가 notify() 또는 notifyAll() 메서드를 호출할 때까지 기다리게 합니다.
notify() wait() 메서드에 의해 일시적으로 중지된 쓰레드 중 하나를 실행 대기 상태로 변경합니다. 여러 개의 쓰레드가 wait()인 경우, 이 중 하나를 임의로 선택해 실행 대기 상태로 변경합니다.
notifyAll() wait() 메서드에 의해 일시적으로 중지된 모든 쓰레드를 실행 대기 상태로 변경합니다.

쓰레드 별 기능 테스트

단일 쓰레드 실행

해당 테스트에서는 main() 함수에 쓰레드를 생성하면 run() 함수에서 이를 수행하고 종료하는 과정을 테스트합니다.

public class SingleThread extends Thread{
    @Override
    public void run() { // Thread 작업을 정의합니다.
        System.out.println("Single Thread를 시작합니다.");
    }

    public static void main(String[] args) {
        SingleThread st = new SingleThread();
        st.start(); //Thread 시작합니다. 시작하게 되면 해당 Thread의 run()를 호출합니다. 
    }
}

 

실행 결과

 

 

멀티 쓰레드 실행

해당 테스트에서는 1개의 쓰레드가 아닌 10개의 쓰레드를 생성하고 종료하는 것을 확인해보는 테스트입니다.

 

[조건]

  1. main() 에서 반복문을 통해 10개의 쓰레드를 생성하고 run() 함수를 10번 수행하면서 반복문의 값(1~10)을 전달하였습니다.
  2. run() 함수에서는 전달받은 값을 기반으로 몇 번째 쓰레드인지 Console을 출력하였고, sleep() 함수를 통해 1초간의 딜레이를 주었습니다.
public class MultiThread extends Thread{
    int seq;

    public MultiThread(int seq){
        this.seq = seq;
    }

    @Override
    public void run(){
        System.out.println("쓰레드" + seq + "를 시작합니다.");
        try{
            Thread.sleep(1000); // 쓰레드 일시 중지
        }catch (InterruptedException  e) {
            throw new RuntimeException(e);
        }
        System.out.println("쓰레드"+seq+"를 종료합니다.");
    }

    public static void main(String[] args) {
        for(int i=0;i<10;i++){
            MultiThread mt = new MultiThread(i);
            
            //Thread를 시작합니다.
            mt.start();
        }
    }
}

 

실행 결과

 

 

정리

  • Thread는 반복문의 순차적으로 시작되지 않습니다.
  • Thread의 종료는 시작한 순서대로 종료되지 않습니다.
  • 메인 메서드가 제일 먼저 종료됩니다.
최근에 올라온 글
Total
Today
Yesterday