자바에서 쓰레드를 구현할 때 2가지 방법이 있다고 한다.
1. Runnable
2. Thread
1. Runnable
Runnable은 이름부터 인터페이스의 느낌이 강하다.
implements Runnable 을 통해서 Runnable 인터페이스를 구현할 수 있다.
Runnable 인터페이스를 구현하게되면
재사용성이 높고, 코드의 일관성을 유지할 수 있어서 Thread보다 더 효율적인 방법이라 할 수 있다.
Runnable 인터페이스는 위와 같이 생겼다.
추상 메서드 run을 반드시 구현해야 한다.
Thread도 run을 구현해야 한다는 점은 같지만 추상 메서드가 아니라 단순 메서드 오버라이딩으로 구현한다.
Runnable 인터페이스를 구현해 스레드 구현
public class Main {
public static void main(String[] args) {
Runnable print100 = new PrintNum(100);
Thread thread1 = new Thread(print100);
Thread thread2 = new Thread(print100);
thread1.start();
thread2.start();
}
}
class PrintNum implements Runnable{
private int lastNum;
public PrintNum(int n){
lastNum = n;
}
@Override
public void run(){
for (int i = 0; i <= lastNum; i++) {
System.out.println(" " + i);
}
}
}
PrintNum 클래스
0부터 입력 받은 n까지의 수를 출력한다.
PrintNum이라는 Runnable 인터페이스를 구현했기 때문에 Thread 객체를 만들 때 재사용 할 수 있음을 볼 수 있다.
2. Thread
상속을 받아 사용해야 하기 때문에 다른 클래스를 상속받아 사용할 수 없다는 단점이 있다.
따라서 일반적으로는 Runnable 인터페이스를 구현해서 스레드를 사용한다.
Thread의 코드를 살짝 살펴보면
Thread 클래스 자체도 Runnable 인터페이스를 가지고 있다.
Thread 클래스 내부에 있는 run 메서드이다.
Thread 상속을 이용한 스레드 구현
public class Main {
public static void main(String[] args) {
PrintChar thread1 = new PrintChar('A', 10);
PrintChar thread2 = new PrintChar('B', 10);
thread1.start();
thread2.start();
}
}
class PrintChar extends Thread{
private char charToPrint;
private int times;
public PrintChar(char c, int t){
charToPrint = c;
times = t;
}
@Override
public void run(){
for (int i = 0; i < times; i++) {
System.out.println(i);
}
}
}
PrintChar 클래스
문자 하나와 숫자 하나를 인자로 받아서, 해당 문자를 숫자만큼 반복해서 출력하는 스레드이다.
thread1, thread2를 각각 만들어서 start 함을 볼 수 있다.
주의사항 : Thread를 실행할 때 start()와 run()의 차이
run()을 호출하는 것은 생성된 스레드 객체를 실행하는 것이 아니라, 단순히 스레드 클래스 내부의 run 메서드를 실행시키는 것이다.
즉, main 함수의 스레드를 그대로 사용해서 run 메서드를 실행하기 때문에 새로운 스레드가 생기지 않고 병렬처리를 할 수 없다.
반면에 start()는 새로운 스레드를 실행하는데 필요한 호출 스택(call stack)을 생성한 다음에 run을 호출해서, 생성된 호출 스택에 run()이 첫 번째로 저장되게 한다.
좀 더 쉽게 말하면, start()를 호출하면 스레드를 새롭게 생성해서 해당 스레드를 runnable 한 상태로 만든 후 run() 메서드를 실행하게 된다. 따라서 start()를 호출해야만 멀티스레드로 병렬 처리가 가능해진다.
그렇다면 추상 메서드로 run() 밖에 존재하지 않는 Runnable은 왜 사용하는 것일까?
Thread를 바로 사용하려면 상속을 받아야 한다. 자바는 다중 상속을 허용하지 않기 때문에 Thread 클래스를 바로 상속받는 경우 다른 클래스를 상속받지 못한다.
하지만 Runnable 인터페이스를 구현한 경우에는 다른 인터페이스를 추가로 구현할 수 있을 뿐만 아니라, 다른 클래스도 상속받을 수 있다.
따라서 클래스의 확장성이 중요하다면 Runnable 인터페이스를 구현해 Thread에 주입해 주는것이 더 좋아 보인다.
start() method
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
'Lang > Java' 카테고리의 다른 글
[Java] Lambda 특징과 활용 (1) | 2022.10.03 |
---|---|
함수형 프로그래밍과 Java #1 (6) | 2022.09.08 |
[디자인 패턴] 싱글톤 패턴 (Creational) (0) | 2022.01.10 |
[Java] 추상 클래스와 인터페이스 (abstract class & interface) (0) | 2021.10.13 |
[Java] Call by Reference? Call by Value! (0) | 2021.08.31 |