Java Thread 1부 - Thread는 실행 흐름이다

청록비
2 read

자바에서 Thread를 어렵게 생각할 필요는 없다.

Thread는 코드를 실행하는 흐름이다.

우리가 자바 프로그램을 실행하면 main() 메서드가 실행된다.

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello Java");
    }
}

이 코드도 그냥 실행되는 것이 아니다.


main이라는 Thread 위에서 실행된다.

public class Main {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
    }
}

실행 결과는 보통 다음과 같다.

main

즉, 자바 프로그램은 기본적으로 main Thread에서 시작한다.


프로세스와 Thread

프로세스는 실행 중인 프로그램이다.

예를 들어 크롬을 실행하면 크롬 프로세스가 만들어지고, IntelliJ를 실행하면 IntelliJ 프로세스가 만들어진다.

Thread는 그 프로세스 안에서 실제로 코드를 실행하는 흐름이다.

쉽게 말하면 이렇다.

개념

비유

프로세스

회사

Thread

회사에서 일하는 직원

회사가 있다고 일이 저절로 진행되지는 않는다.
직원이 있어야 일이 처리된다.

프로그램도 마찬가지다.
프로세스가 있어도 실제 코드를 실행하는 Thread가 필요하다.


왜 Thread를 사용할까?

이유는 단순하다.

여러 작업을 동시에 처리하기 위해서다.

예를 들어 프로그램에서 다음 일을 처리한다고 해보자.

  • 파일 다운로드

  • 사용자 입력 처리

  • 화면 갱신

  • 로그 저장

이 작업을 하나의 흐름에서만 처리하면, 파일 다운로드가 끝날 때까지 다른 작업이 멈출 수 있다.

하지만 Thread를 나누면 오래 걸리는 작업을 따로 처리할 수 있다.

main Thread     → 화면 처리
worker Thread   → 파일 다운로드

이렇게 하면 프로그램이 멈추지 않고 자연스럽게 동작할 수 있다.


Thread 만들기 1 - Thread 상속

자바에서 Thread를 만드는 첫 번째 방법은 Thread 클래스를 상속하는 것이다.

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("실행 중: " + Thread.currentThread().getName());
    }
}

실행은 이렇게 한다.

public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();

        System.out.println("main Thread 실행");
    }
}

핵심은 start()다.

start()를 호출해야 새로운 Thread가 만들어진다.


Thread 만들기 2 - Runnable 구현

두 번째 방법은 Runnable을 구현하는 것이다.

class MyTask implements Runnable {
    @Override
    public void run() {
        System.out.println("실행 중: " + Thread.currentThread().getName());
    }
}

실행은 이렇게 한다.

public class Main {
    public static void main(String[] args) {
        Runnable task = new MyTask();
        Thread thread = new Thread(task);

        thread.start();
    }
}

실무에서는 보통 Runnable 방식이 더 자주 사용된다.

이유는 간단하다.

Thread는 실행 흐름이고,
Runnable은 실행할 작업이다.

작업과 실행 흐름을 분리하면 코드가 더 깔끔해진다.


람다식으로 더 짧게 만들기

Runnable은 람다식으로 간단하게 만들 수 있다.

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("새 Thread 실행");
        });

        thread.start();
    }
}

더 줄이면 이렇게도 가능하다.

new Thread(() -> {
    System.out.println("작업 실행");
}).start();

다만 실무에서는 Thread를 직접 계속 만들기보다, 보통 Thread Pool을 사용한다.

Thread Pool은 이후 글에서 다룬다.


start()와 run()은 완전히 다르다

Thread에서 가장 중요한 부분이다.

Thread thread = new Thread(() -> {
    System.out.println(Thread.currentThread().getName());
});

thread.run();

실행 결과는 보통 다음과 같다.

main

run()을 직접 호출하면 새 Thread가 만들어지지 않는다.
그냥 일반 메서드를 호출한 것과 같다.

반면 start()를 호출하면 다르다.

Thread thread = new Thread(() -> {
    System.out.println(Thread.currentThread().getName());
});

thread.start();

실행 결과는 보통 다음과 같다.

Thread-0

정리하면 이렇다.

호출

결과

run()

현재 Thread에서 그냥 메서드 실행

start()

새로운 Thread를 만들고 그 안에서 run() 실행

Thread를 새로 실행하고 싶다면 반드시 start()를 사용해야 한다.


실행 순서는 보장되지 않는다

다음 코드를 보자.

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("worker Thread");
        });

        thread.start();

        System.out.println("main Thread");
    }
}

결과는 이렇게 나올 수도 있고,

main Thread
worker Thread

이렇게 나올 수도 있다.

worker Thread
main Thread

왜냐하면 어떤 Thread를 먼저 실행할지는 운영체제가 결정하기 때문이다.

그래서 Thread 코드는 실행 순서를 함부로 믿으면 안 된다.


Thread는 한 번만 start() 할 수 있다

하나의 Thread 객체는 한 번만 실행할 수 있다.

Thread thread = new Thread(() -> {
    System.out.println("Thread 실행");
});

thread.start();
thread.start();

이 코드는 예외가 발생한다.

java.lang.IllegalThreadStateException

같은 작업을 다시 실행하고 싶다면 새 Thread 객체를 만들어야 한다.

Runnable task = () -> {
    System.out.println("Thread 실행");
};

new Thread(task).start();
new Thread(task).start();

핵심 정리

Thread 1부의 핵심은 이것만 기억하면 된다.

  1. Thread는 코드를 실행하는 흐름이다.

  2. 자바 프로그램은 main Thread에서 시작한다.

  3. Thread는 여러 작업을 동시에 처리하기 위해 사용한다.

  4. Thread 상속 방식보다 Runnable 방식이 더 유연하다.

  5. run()은 그냥 메서드 호출이다.

  6. start()를 호출해야 새 Thread가 만들어진다.

  7. Thread 실행 순서는 보장되지 않는다.

  8. 하나의 Thread 객체는 한 번만 실행할 수 있다.

  9. Thread는 여기서부터 시작이다.

  10. 다음 글에서는 Thread의 상태와 제어 방법을 다룬다.

sleep(), join(), interrupt()가 왜 필요한지 알아보자.

#Cloud#Infrastructure#Serverless#Tech2024