쓰레드를 구현하는 방법은 2가지
Thread
클래스 상속 받기
Runnable
인터페이스 구현하기
두 방법 모두 run()
이라는 메서드를 구현하는 것으로 끝.
run()
의 구현부에는 쓰레드로 작업하고자 하는 내용을 넣으면 됨.
다만, 1번을 사용하면 다른 클래스를 상속받을 수 없게 된다는 제약이 있어, 일반적으로 2번 방법을 사용함.
그런데 Thread
클래스를 상속 받는 경우와 Runnable
인터페이스를 구현하는 경우는 인스턴스 생성 방식이 다름
class ThreadExample {
public static void main(String[] args) {
ThreadA a = new ThreadA(); // 일반적인 인스턴스 생성 방법과 동일
Runnable r = new ThreadB(); // 우선 Runnable을 구현한 클래스의 인스턴스를 생성하고,
Thread b = new Thread(r); // Thread의 생성자에 매개변수로 넘겨줘야함.
a.start();
b.start();
}
}
class ThreadA extends Thread {
public void run() {
System.out.println(getName()); // (1)
}
}
class ThreadB implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName()); // (2)
}
}
실행 결과)
Thread-0
Thread-1
💡 왜 Runnable
구현체의 인스턴스를 Thread
의 생성자 매개변수로 넘기는가?
ThreadB
처럼 인스턴스를 생성한 후에 그걸 Thread
의 생성자에 매개변수로 넘기는 이유는 객체지향 프로그래밍에서 추구하는 캡슐화와 관련되어 있음.
Thread
클래스를 단순화시켜 표현하면 다음과 같은데,
public class Thread {
private Runnable r;
public Thread(Runnable r) {
this.r = r; // Runnable 구현 클래스의 인스턴스를 생성자의 매개변수로 받아와 Thread의 인스턴스 변수 값으로 지정.
}
public void run() {
if (r != null) r.run(); // Runnable 구현 클래스의 인스턴스가 가진 run 메서드를 호출
}
}
Runnable
구현체가 가진 run()
메서드는 선언부가 Runnable
에서 정의한 run()
의 선언부와 동일하게 되어 있어, 호출하는 입장에서는 인터페이스에 정의된 선언부만 확인하면 됨.
만약 ThreadB
가 Runnable
을 구현하지 않고, Thread
의 생성자가 직접 ThreadB
의 인스턴스를 매개변수로 받아왔다면 ThreadB
가 가진 run()
을 호출하는데에 있어 번거로움이 생김. run()
의 선언부에 변경 사항이 생기면 그것을 호출하는 Thread
쪽에서도 변경 사항에 맞게 수정을 해야하고, ThreadB
가 아닌 다른 클래스를 사용하고자 할 때 다시 그 클래스의 메서드 스펙에 맞게 호출 파트를 수정해야함.
따라서 Runnable
은 run()
을 호출하는 쪽과 구현하는 쪽이 직접적 의존 관계에 있지 않게 하고, 둘 사이의 중간 다리 역할이자 run()
메서드 선언을 표준화 시키는 역할을 함.
💡 getName()
을 호출하는 방식이 왜 다른가?
Thread
를 상속받은 ThreadA
는 Thread
가 가진 메서드를 직접 호출해서 자유롭게 사용할 수 있지만 (주석 1번)
Thread
를 상속받지 않은 ThreadB
는 Thread
의 static 메서드인 currentThread()
로 현재 실행중인 쓰레드의 참조를 얻어온 상태에서만 Thread
의 메서드를 사용할 수 있음. (주석 2번)
💡 쓰레드의 이름은 바꿀 수 없는가?
쓰레드의 이름은 기본적으로 Thread-0
, Thread-1
, Thread-2
, ... 지만
Thread
생성자에 이름을 지정해주거나,
Thread(Runnable r, String name)
Thread(String name)
setName()
메서드로 이름을 지정해주면 바꿀 수 있음
void setName(String name)
쓰레드를 생성한 이후에 start()
을 호출해야 실행시킬 수 있음.
start()
이 호출되면, 해당 쓰레드는 실행 대기 상태에 들어갔다가 자기 차례가 오면 실행됨. (대기자가 없으면 바로 실행)
❗️ [주의]
실행 종료된 쓰레드는 다시 실행할 수 없음.
따라서 한 쓰레드에 대해 start()
은 한 번만 쓸 수 있으며, 같은 쓰레드를 다시 실행시키려면 쓰레드 인스턴스를 새로 생성한 다음에 start()
을 다시 호출해야함.
ThreadA a = new ThreadA();
a.start();
a = new ThreadA(); // 새 인스턴스를 생성하고 참조변수에 재할당
a.start();