스레드 시작1
- 메서드 영역(Method area): 메서드 영역은 프로그램을 실행하는데 필요한 공통 데이터를 관리한다. 이 영역은 프로그램의 모든 영역에서 공유한다.
- 클래스 정보: 클래스의 실행 코드(바이트 코드), 필드, 메서드와 생성자 코드등 모든 실행 코드가 존재한다.
- static 영역: static 변수들을 보관한다.
- 런타임 상수 풀: 프로그램을 실행하는데 필요한 공통 리터럴 상수를 보관한다.
- 스택 영역(Stack Area): 자바 실행 시, 하나의 실행 스택이 생성된다. 각 스택 프레임은 지역 변수, 중간 연산 결과, 메서드 호출 정보 등을 포함한다.
- 스택 프레임: 스택 영역에 쌓이는 네모 박스가 하나의 스택 프레임이다. 메서드를 호출할 때 마다 하나의 스택 프레임이 쌓이고, 메서드가 종료되면 해당 스택 프레임이 제거된다.
- 힙 영역(Heap Area): 객체(인스턴스)오 배열이 생성되는 영역이다. 가비지 컬렉션(GC)이 이루어지는 주요 영역이며, 더 이상 참조되지 않는 객체는 GC에 의해 제거된다.
참고: 스택 영역은 더 정확히는 각 스레드별로 하나의 실행 스택이 생성된다. 따라서 스레드 수 만큼 스택이 생성된다. 지금은 스레드를 1개만 사용하므로 스택도 하나이다. 이후 스레드를 추가할 것인데, 그러면 스택도 스레드 수 만큼 승가한다.
스레드 생성
스레드를 직접 만들어보자. 그래서 해당 스레드에서 별도의 로직을 수행해보자.
스레드를 만들 때는 Thread 클래스를 상속 받는 방법과 Runnable 인터페이스를 구현하는 방법이 있다.
먼저 Thread 클래스를 상속 받아서 스레드를 생성해보자.
스레드 생성 - Thread 상속
자바는 많은 것을 객체로 다룬다. 자바가 예외를 객체로 다루듯이, 스레드도 객체로 다룬다.
스레드가 필요하면, 스레드 객체를 생성해서 사용하면 된다.
public class HelloThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": run()");
}
}
Thread 클래스를 상속하고, 스레드가 실행할 코드를 run() 메서드에 재정의한다.
Thread.currentThread()를 호출하면 해당 코드를 실행하는 스레드 객체를 조회할 수 있다.
Thread.currentThread().getName(): 실행 중인 스레드의 이름을 조회한다.
package thread.start;
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");
}
}
앞서 만든 HelloThread 스레드 객체를 생성하고 start() 메서드를 호출한다.
start() 메서드는 스레드를 실행하는 아주 특별한 메서드이다.
start()를 호출하면 HelloThread 스레드가 run() 메서드를 실행한다.
주의! run() 메서드가 아니라 반드시 Start() 메서드를 호출해야 한다. 그래야 별도이 스레드에서 run() 코드가 실행된다.
스레드 시작2
데몬 스레드
데몬 스레드
스레드는 사용자(user) 스레드와 데몬(daemon) 스레드 2가지 종류로 구분할 수 있다.
사용자 스레드(non-daemon) 스레드
- 프로그램의 주요 작ㅇ버을 수행한다.
- 작업이 완료될 때까지 실행된다.
- 모든 user 스레드가 종료되면 JVM도 종료된다.
데몬 스레드
- 백그라운드에서 보조적은 작업을 수행한다
- 모든 user 스레드가 종료되면 데몬 스레드는 자동으로 종료된다
JVM은 데몬 스레드의 실행 완료를 기다리지 않고 종료된다. 데몬 스레드가 아닌 모든 스레드가 종료되면, 자바 프로그램도 종료된다.
용어 - 데몬: 그리스 신화에서 데몬은 신과 인간 사이의 중간적 존재로 보이지 않게 활동하며 일상적인 일들을 도왔다. 이런 의미로 컴퓨터 과학에서는 사용자들에게 직접적으로 보이지 않으면서 시스템의 백그라운드에서 작업을 수행하는 것을 데몬 스레드, 데몬 프로세스라 한다. 예를 들어서 사용하지 않는 파일이나 메모리를 정리하는 작업들이 있다.
package thread.start;
public class DaemonThreadMain {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + ": main() start");
Daemonthread daemonthread = new Daemonthread();
daemonthread.setDaemon(true); // 데몬 스레드 여부
daemonthread.start();
System.out.println(Thread.currentThread().getName() + ": main() end");
}
static class Daemonthread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": run()");
try {
Thread.sleep(10000); // 10초간 실행
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + ": run() end");
}
}
}
setDaemon(true): 데몬 스레드로 설정한다.
데몬 스레드 여부는 start() 실행 전에 결정해야 한다. 이후에는 변경되지 않는다.
기본 값은 false이다. (user 스레드가 기본)
참고: run() 메서드 안에서 Thread.sleep()를 호출할 때 체크 예외인 InterruptedException을 밖으로 던질 수 없고 반드시 잡아야 한다. run() 메서드는 체크 예외를 밖으로 던질 수 없는데, 이 부분은 뒤에서 설명한다.
스레드 생성 -Runnable
스레드를 만들 때는 Thread 클래스를 상속 받는 방법과 Runnable 인터페이스를 구현하는 방법이 있다.
앞서 Thread 클래스를 상속 받아서 스레드를 생성해보았다.
이번에는 Runnable 인터페이스르 ㄹ구현하는 방식으로 스레드를 생성해보자.
Thread 상속 vs Runnable 구현
스레드 사용할 때는 Thread를 상속 받는 방법보다 Runnable 인터페이스를 구현하는 방식을 사용하자.
두 방식이 서로 장단점이 있지만, 스레드를 생석할 때는 Thread 클래스를 상속하는 방시보다 Runnable 인터페이스를 구현하는 방식이 더 나은 선택이다.
Thread 클래스 상속 방식
장점
간단한 구현: Thread 클래스를 상속받아 run() 메서드만 재정의하면 된다.
단점
상속의 제한: 자바의 단일 상속만을 허용하므로 이미 다른 클래스를 상속받고 있는 경우 Thread 클래스를 상속 받을 수 없다.
유연성 부족: 인터페이스를 사용하는 방법에 비해 유연성이 떨어진다.
Runnable 인터페이스를 구현 방식
장점
상속의 자유로움: Runnable 인터페이스 방식은 다른 클래스를 방속받아도 문제없이 구현할 수 있다.
코드의 분리: 스레드와 실행할 작업을 분리하여 코드의 가독성을 높일 수 있다.
여러 스레드가 동일한 Runnable 객체를 공유할 수 있어 자원 관리를 효율적으로 할 수 있다.
단점
코드가 약간 복잡해질 수 있다. Runnable 객체를 생성하고 이를 Thread에 전달하는 과정이 추가된다.
정리하자면 Runnable 인터페이스를 구현하는 방식을 사용하자. 스레드와 실행할 작업을 명확히 분리하고, 인터페이스를 사용하므로Thread 클래스를 직접 상속하는 방식보다 더 유연하고 유지보수 하기 쉬운 코드를 만들 수 있다.
로거 만들기
여러 스레드 만들기
Runnable을 만드는 다양한 방법
중첩 클래스를 사용하면 Runnable을 더 편리하게 만들 수 있다.
(중첩 클래스는 자바 중급 1편 참고)
참고로 모두 결과는 같다
정적 중첩 클래스 사용
package thread.start;
import static util.MyLogger.log;
public class InnerRunnableMainV1 {
public static void main(String[] args) {
log("main() start");
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
log("main() end");
}
static class MyRunnable implements Runnable {
@Override
public void run() {
log("run()");
}
}
}
- 특정 클래스 안에서만 사용되는 경우 이렇게 중첩 클래스를 사용하면 된다.
익명 클래스 사용
package thread.start;
import static util.MyLogger.log;
public class InnerRunnableMainV2 {
public static void main(String[] args) {
log("main() start");
Runnable runnable = new Runnable() {
@Override
public void run() {
log("run()");
}
};
Thread thread = new Thread(runnable);
thread.start();
log("main() end");
}
}
- 특정 메서드 안에서만 간단히 정의하고 사용하고 싶다면 익명 클래스를 사용하면 된다.
익명 클래스 변수 없이 직접 전달
package thread.start;
import static util.MyLogger.log;
public class InnerRunnableMainV3 {
public static void main(String[] args) {
log("main() start");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
log("run()");
}
});
thread.start();
log("main() end");
}
}
- 익명 클래스를 참조하는 변수를 만들지 않고 직접 전달할 수 있다.
람다
package thread.start;
import static util.MyLogger.log;
public class InnerRunnableMainV4 {
public static void main(String[] args) {
log("main() start");
Thread thread = new Thread(() -> log("run()"));
thread.start();
log("main() end");
}
}
- 람다를 사용하면 메서드(함수) 코드 조각을 전달할 수 있다.
- 우리는 아직 람다를 학습하지 않았기 때문에 정적 중첩 클래스나 익명 클래스를 주로 사용하겠다.
문제와 풀이
정리
출처 - 김영한의 실전 자바 - 고급 1편
'Backend > Java' 카테고리의 다른 글
자바 고급 1편 - 메모리 가시성 (0) | 2024.11.17 |
---|---|
자바 고급 1편 - 스레드 제어와 생명 주기2 (0) | 2024.11.11 |
자바 고급 1편 - 프로세스와 스레드 소개 (4) | 2024.10.31 |
트랜잭션 이해 (0) | 2024.08.30 |
커넥션풀과 데이터소스 이해 (0) | 2024.08.27 |