Thread 와 Runnable
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상태로 전환
- 스레드 생성이 완료되면 OS 스케줄러의 준비 큐에 배치하고, 자바 스레드 상태로는
-
스케줄링 차례가 오면 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 시스템 콜로 인해 매번 스레드를 생성하는 오버헤드가 발생한다.