Java

[Java] Thread

DH_0518 2024. 2. 25. 11:57

스레드에 대해 공부하기 전에, 프로세스(Process)와 스레드(Thread)는 프로그램의 실행과 동작을 통해 설명할 수 있기에, 프로그램의 실행에 대해 먼저 알아보자

 

 

Program의 실행

 

 

프로그램(Program)과 프로세스(Process)는 둘 다 어떠한 목적을 위해 처리 방법과 순서를 기술한 코드의 집합체를 말하는데, 다만 프로그램은 저장 장치에 저장된 '정적'인 상태를 말하고, 프로세스는 실행을 위해 메모리 공간에 올라와있는 '동적'인 상태를 의미한다.

즉, 프로그램과 프로세스는 똑같은 프로그램을 말하지만, 상태만 다르다는 의미이다.

 

다시 프로그램의 실행으로 넘어가보자.

우리는 특정 프로그램을 실행시키기 위해 컴퓨터에 저장하고 설치한다. 이후에 프로그램(Program)을 실행시키면 OS에서 그 프로그램을 위한 메모리 공간을 할당받고, 그 메모리 공간에 프로그램이 올라가서 실행된다. 이때 메모리 공간에 올라가서 실행되는 프로그램을 프로세스(Process)라 말하는 것이다.

 

그렇다면 스레드는 무엇일까?

Thread는 CPU에 작업 요청을 하는 실행 단위를 말한다. OS는 프로그램을 실행시키기 위해 프로그램의 코드와 데이터를 메모리에서 가져오고, 프로세스 제어 블록(PCB)을 생성하고, 작업에 필요한 메모리를 확보한 후, 준비된 프로세스를 ready queue에 삽입한다.

 

이렇게 프로세스가 생성되면 CPU 스케쥴러는 프로세스를 실행시키기 위해 해야 하는 일을 CPU에게 전달하고, CPU가 그 일을 하게 된다. 이때 CPU가 받는 일을 스레드(Thread)라고 하는 것이다.

 

즉, OS 입장에서는 작업의 단위가 프로세스(Process)가 되지만, CPU 입장에서 작업의 단위는 스레드(Thread)인 것이다.

따라서 CPU가 한 번에 많은 양의 쓰레드를 처리할 수 있다면 성능이 좋다고 말할 수 있다.

 

 

 

 

멀티 태스킹(Multi-tasking)

 

CPU 코어는 한번에 하나의 일만 처리할 수 있다. 즉, CPU 코어 한 개당 한 개의 프로세스나 스레드만 처리할 수 있다는 것이다. 그렇다면 어떻게 우리는 컴퓨터로 음악을 들으면서 웹서핑도 하고 작업도 하는 등의 여러 작업을 병렬적으로 동시에 할 수 있을까?

 

실제로 CPU 코어에서 각 작업들이 동시에 처리되는 것이 아니라, 아주 짧은 시간 동안 여러 스레드나 프로세스를 번갈아가면서 처리를 하기에 동시에 동작하는 것처럼 보일 뿐이다.

 

이렇게 여러 작업을 동시에 처리할 수 있도록 프로세스와 스레드를 빠르게 번갈아가며 처리하는 것을 멀티 태스킹이라 한다

 

 

 

 

 

Java에서 Thread 구현

 

그렇다면 앞서 살펴봤던 스레드를 직접 구현해보자. 자바에서는 두 가지 방식을 사용하여 구현할 수 있다. 두 방식에 큰 차이는 없고, 그저 쓰레드를 통해 작업하고자 하는 내용으로 run() 메서드의 바디를 채우기만 하면 된다.

  • Runnable 인터페이스 구현
  • Thread 클래스 상속

Runnable 인터페이스 구현

  • Thread를 상속받으면 다른 클래스를 상속받을 수 없기에 Runnable 인터페이스를 구현하는 방법이 더 많이 사용된다
  • 재사용성이 높고 코드의 일관성을 유지할 수 있음
  • run() 메서드를 오버라이딩해서 사용
  • 인스턴스를 생성하기 위해 구현한 클래스를 Thread 생성자에 argument로 넘겨줘야 한다
  • Runnable 인터페이스는 run()만 구현되어 있다
// Runnable 인터페이스 구현
public class RunnableThread implements Runnable {
    @Override
    public void run() {
        // 수행코드
        System.out.println("Thread is running");
    }
}

// 쓰레드 인스턴스 생성
public static void main(String[] args) {
    // Runnable을 구현한 클래스로 Runnable 생성
    Runnable r = new RunnableThread();
    // 구현한 클래스를 파라미터로 넘겨서 Thread 생성
    Thread t = new Thread(r, "runnableThread");
}

Runnable 인터페이스

 

 

Thread 클래스 상속

  • run() 메서드를 오버라이딩해서 사용
  • 다른 파라미터를 넘겨줄 필요 없이, new로 바로 인스턴스를 생성할 수 있다
// Thread를 상속받아 구현
public class ExtendsThread extends Thread {
    @Override
    public void run() {
        // 수행코드
        System.out.println("Thread is running");
    }
}

// Thread 인스턴스 생성
public static void main(String[] args) {
    // 파라미터 없이 곧바로 Thread 인스턴스 생성
    ExtendsThread t1 = new ExtendsThread();
}

 

 

 

 

멀티스레드와 싱글스레드 동작

 

앞서 구현한 스레드는, 오버라이딩한 run() 메서드를 사용하는 방법과 start() 메서드를 사용하여 실행하는 방법이 있다.

 

Java에는 실질적인 명령어들을 담고 있는 메모리인 콜 스택(call stack)이 존재하는데, 한번의 작업에 하나 이상의 콜 스택을 사용한다. 따라서 멀티 스레딩을 사용하려면 두 개 이상의 콜 스택이 필요한데, 만약 run() 메서드를 사용한다면 main()의 콜 스택 하나만 이용하는 것이기 때문에 스레드를 활용한다기 보다는 스레드 객체의 run() 메서드를 호출하는 것 뿐이다.

 

반대로 start() 메서드를 호출하면, JVM은 자동으로 스레드를 위한 콜 스택을 새로 만들어주고, context switching을 통해 스레드답게 동작하게 해준다. 따라서 멀티 태스킹을 하려면 반드시 start() 메서드를 사용해야한다!

 

 

run() 메서드 사용

  • Thread가 생성되지 않으며, 그냥 target의 run() 메서드를 호출한다
  • 호출 횟수에 제한 없이 계속 호출할 수 있다
  • Thread가 생성되지 않으므로, 싱글 스레드로 동작한다
// Thread.java 클래스의 run() 메서드
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

 

start() 메서드 사용

  • JVM의 native 영역에서 새로운 Thread가 생성되며, Thread가 시작되면 run() 메서드가 실행된다
  • 동일한 스레드 객체에서 두 번 이상 호출 시 IllegerThreadStateException 예외가 발생되므로, 한번만 호출할 수 있다. 따라서 동일한 작업을 다시 실행하고 싶다면 새로운 쓰레드 객체를 만들어서 실행해야 한다
  • 매번 새로운 스레드를 만들어야 하므로 오버헤드가 발생할 수 있지만, 혹시라도 예외가 발생한 스레드가 재실행될 수도 있으므로 동일한 객체의 재실행을 막는 것이고, 또한 Thread pool을 통해 오버헤드를 줄이고 성능을 최적화할 수 있다
  • 멀티스레드로 동작한다
// Thread.java 클래스의 start() 메서드
public synchronized void start() {
        // 동일한 스레드 객체가 두 번 실행될 수 없다
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
             /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
            }
        }
    }

 

 

 

 

스레드의 실행제어

 

스레드는 생성부터 실행, 종료까지 총 5가지의 상태가 존재한다

 

스레드의 실행제어

  • NEW : 스레드가 생성되고 아직 start()가 호출되지 않은 상태
  • RUNNABLE : 실행중, 또는 실행 가능 상태
  • BLOCKED : 동기화 블럭에 의해 일시정지된 상태(lock이 풀릴 때까지 기다림)
    • blocked는 synchronized 메서드나 블록을 수행하는 상황에서, 해당 공유객체에 접근하는 다른 스레드들이 얻게되는 상태변화이다
  • WATING, TIME_WATING : 실행가능하지 않은 일시정지 상태
    • sleep() 메서드나 wait(), join() 메서드등을 사용하면 이뤄지는 상태변화이다
  • TERMINATED : 스레드 작업이 종료된 상태

 

 

 

 

 

 

 

 

 

 

 

 

Reference

 

쓰레드(Thread)의 기본개념

프로그램과 프로세스 우리가 프로그램을 실행하려고 하면, 실행을 위해 운영체제에서 메모리 공간을 할당받아오게 되며, 그 공간에 프로그램이 올려져 실행되게 된다. 즉 프로세스는 실행중인

simple-ing.tistory.com

 

[JAVA] 쓰레드 구현 및 실행 (Thread, Runnable, start(), run())

1. 쓰레드 구현 쓰레드를 구현하는 방법은 Thread 클래스를 상속받는 방법과 Runnable 인터페이스를 구현하는 방법, 두가지가 있다. 각각의 방식으로 구현은 다음과 같이 할 수 있다. // Thread 클래스

jammdev.tistory.com

 

[Java] Thread | 👨🏻‍💻 Tech Interview

[Java] Thread 요즘 OS는 모두 멀티태스킹을 지원한다. 멀티태스킹이란? 예를 들면, 컴퓨터로 음악을 들으면서 웹서핑도 하는 것 쉽게 말해서 두 가지 이상의 작업을 동시에 하는 것을 말한다. 실제

gyoogle.dev

 

[JAVA] - Thread.start()와 Thread.run()의 차이

JAVA로 Thread 관련 프로그래밍을 학습하다보면 start() 메서드와 run() 메서드를 보게되는데 두 메서드를 실행하게되면 Thread의 run() 메서드를 실행하게 된다. 다만 이 두 메서드의 동작방식을 제대로

kim-jong-hyun.tistory.com