Thread 와 Runnable
자바 5 이전
Thread
스레드 생성을 위해 자바에서 미리 구현해둔 클래스이며, 유저 수준의 스레드이다.
스레드를 생성하는 방법
스레드 객체를 활용하여 멀티 스레드로 논리적 흐름을 분리하고 싶다면 Thread 상속 및 run 메서드 오버라이딩을 통해 새로운 스레드에서 코드를 실행할 수 있다.
public class ThreadExample extends Thread{
@Override
public void run() {
System.out.println("스레드 예시 실행");
}
}
...
ThreadExample thread = new ThreadExample();
thread.start();
start()메서드를 호출 했는데 어떻게run()메서드가 실행 되는걸까?
public void start() {
synchronized (this) {
// zero status corresponds to state "NEW".
if (holder.threadStatus != 0)
throw new IllegalThreadStateException();
start0();
}
}
단순 오버라이딩 된 메서드인 run() 을 호출하면, 메인 스레드에서 객체의 메서드를 호출하는 것 과 다름없다.
메인 스레드와 분리 되어 새로운 스레드의 공간을 할당하고, 코드를 실행하는 데에 있어 JVM 의 도움이 필요하다.
start() 메서드를 호출하면 JVM 에서 일어나는 과정
JNI를 통해 OS의 커널에 스레드 생성을 요청
스레드 생성을 위한 공간 할당
OS는 새로운 스레드를 위해 커널 스택과 TCB 등의 자원 할당
커널 스레드의 정보를 자바 스레드 객체에 업데이트
커널 스레드의 스레드 ID, 상태, 이름 등
JVM은 해당 스레드가 자바 코드를 실행하기 위한 런타임 데이터 영역 초기화
OS 스케줄러 큐에 해당 스레드 배치
스레드 생성이 완료되면 OS 스케줄러의 준비 큐에 배치하고, 자바 스레드 상태로는
RUNNABLE상태로 전환
스케줄링 차례가 오면 CPU 자원을 할당 받아 run() 메서드 실행
주요 특징
유저 스레드와 커널 스레드가 1:1 매핑되어 블락이 가능하고, 블락 시 다른 스레드를 준비 큐에서 실행 시킬 수 있다.
start()명령어를 호출하면 JNI 를 통해 OS 에게 시스템 콜을 보내어 스레드를 생성하고 매핑하는 과정이 오버헤드가 발생한다.run()메서드를 오버라이드 하고, run() 메서드가 아닌start()메서드를 실행 해야 멀티 스레드 환경에서 코드를 실행할 수 있다.Thread는 새로운 객체의 상속 관계를 만들며, 다중 상속이 불가능한 자바에서 상속할 수 있는 부모 타입이 제한된다.
Runnable
스레드를 조작하는 run() 메서드만 제공하는 함수형 인터페이스이다. 람다식으로 구현할 수도 있고, 새로운 객체를 생성하여 구현할 수도 있다.
/**
* Represents an operation that does not return a result.
*
* <p> This is a {@linkplain java.util.function functional interface}
* whose functional method is {@link #run()}.
*
* @author Arthur van Hoff
* @see java.util.concurrent.Callable
* @since 1.0
*/
@FunctionalInterface
public interface Runnable {
/**
* Runs this operation.
*/
void run();
}Runnable 인터페이스는 결과를 반환하지 않는 메서드를 제공한다. 그렇다는건 새로운 객체를 생성하여 run() 을 직접 구현하더라도, 스레드가 실행한 흐름에 대한 결과를 반환하지 못한다.
스레드를 생성하는 방법
public class ThreadExample implements Runnable{
@Override
public void run() {
System.out.println("스레드 예시 실행");
}
}
...
Thread thread = new Thread(new ThreadExample());
thread.start();public class ThreadMain {
public static void main(String[] args) {
Thread thread = new Thread(() -> System.out.println("스레드 예시 실행"));
thread.start();
}
}자바에서 단순하게 스레드를 다루어야 한다면, 부모의 많은 기능을 상속 받는 Thread 보다 Runnable 을 익명 객체로 제공하든, 새로운 객체를 생성하여 구현체로 다루든 한 가지 메서드에 대한 기능만 구현하는 것이 더 효율적이다.
그렇기 때문에 대부분의 상황에서 Runnable 인터페이스를 구현하여 문제를 해결할 수 있다.
Thread 와 Runnable 의 한계
run()메서드를void로 제공하기 때문에 반환 값을 사용할 수 없다.start()명령어가 실행 되면 JNI 의 OS 시스템 콜로 인해 매번 스레드를 생성하는 오버헤드가 발생한다.
Last updated