본문 바로가기
Java

Java Thread & Runnable 정리

by fygoo-826 2025. 2. 7.
728x90
반응형

 Java에서 스레드를 사용하는 것은 멀티태스킹을 구현하는 중요한 방법이다. 이전 포스팅에 이어서 이번에는 Java 에서 스레드를 활용하는 아주 기본적인 방법을 공유해보고자 한다.

 


Thread 스택 영역

[Figure 1] JVM 메모리 구조, 쓰레드 별 분리된 Stack 영역

 

 OS의 원리와 유사하게, 프로세스는 코드 영역, 데이터 영역, 스택 영역, 힙 영역으로 구성된다. 이때 각 프로세스의 스레드는 자신만의 스택 영역을 가지며, 힙, 데이터, 코드 영역은 공유하여 사용한다. 자바 또한 JVM 아래에서 이러한 구조를 유지하며, 각각의 스레드는 독립적인 스택 영역을 갖는다.

 

Java 에서 Thread 클래스

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

public class HelloThreadMain {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + ": main() start");

        HelloThread helloThread = new HelloThread();
        System.out.println(Thread.currentThread().getName() + ": start() 호출 전");
        helloThread.start();
        System.out.println(Thread.currentThread().getName() + ": start() 호출 후");

        System.out.println(Thread.currentThread().getName() + ": main() end");
    }
}

 위 코드는 두 개의 스레드(main 스레드와 helloThread) 작동을 보여준다. 새로운 스레드를 생성하기 위해 Thread 클래스를 상속받은 커스텀 스레드 클라스를 만들고 run() 메서드를 오버라이딩 했다.

 start() 메서드를 호출하면 새 스레드가 run()을 지연 호출하게 된다. 결과적으로, main()과 Thread-0의 실행 순서는 OS의 스케줄링에 의해 예측할 수 없는 방식으로 교차하게 된다. 이는 스레드 간의 실행 순서가 보장되지 않음을 의미한다. 여러번 실행 해보자!!

 

Daemon Thread

 OS 공부를 하다보면 만나는 개념 중 하나이다. 스레드는 사용자(user) 스레드와 데몬(daemon) 스레드로 나눌 수 있다. 데몬 스레드는 백그라운드에서 보조 작업을 수행하고, 모든 user 스레드가 종료되면 자동으로 종료된다. 이 개념은 그리스 신화에서 데몬이 신과 인간 사이의 중개자로 여겨지는 것에서 유래하였다. 따라서 데몬 스레드는 보이지 않게 백그라운드에서 활동하며, 일상적인 작업을 돕는 역할을 한다.

 

Runnable Interface

public class ManyThreadMainV2 {
    public static void main(String[] args) {
        System.out.println("main() start");

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i < 6; i++) {
                    System.out.println("value : " + i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };

        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
        }
        
        System.out.println("main() end");
    }
}

 `Thread` 클래스를 사용할 때 Runnable 인터페이스를 사용하는 방법도 있다. 위 예제에서는 익명 클래스 방식으로 runnable 을 구현해서 실행시킨 예제이다.

 

Thread vs Runnable??

extends Thread
 run() 메서드만 오버라이딩하면 되므로 구현이 간단하다. 그러나 상속의 제한이 존재하고 유연성이 떨어진다.

implements Runnable

 코드가 약간 복잡해질 수 있으나, 인터페이스를 통해 다른 클래스를 상속받는 것에 문제 없으며, 스레드와 실행할 작업(비즈니스 로직)을 분리할 수 있어 단일 책임 원칙을 유지할 수 있다. 또한, 여러 스레드가 동일한 Runnable 객체를 공유할 수 있다.

 

public class InnerRunnableMainV4 {
    public static void main(String[] args) {
        System.out.println("main() start");

        Thread thread = new Thread(() -> System.out.println("run()"));
        thread.start();

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

람다식과 함께 활용하면 훨씬 효율적이게 된다.


 이번 포스팅에서는 Java의 스레드와 스택 영역, 데몬 스레드 및 Runnable 인터페이스의 사용에 대한 내용을 살펴보았다. 각 개념들을 충분히 이해하면 멀티쓰레드 프로그래밍의 효율성을 극대화하는 데 도움이 될 것이다.

728x90
반응형