자바에서 여러가지 일을 동시에 처리하기 위해 사용되는 것이 스레드(Thread)이다. 먼저 프로세스와 스레드를 알아보자
프로세스
프로세스(Process)는 일반적으로 cpu에 의해 실행중인 프로그램을 말하고, 자신만의 메모리 공간을 포함한 독립적 실행 환경을 가지고 있다. 우리가 사용하는 프로그램의 일부는 여러 프로세스간 상호작용을 하는 것일수 있다.
자바JVM(Java Virtual Machine)은 주로 하나의 프로세스로 실행되며, 동시에 여러 작업을 수행하기 위해 멀티 스레드를 지원하고 있다.
스레드
스레드(Thread)는 프로세스안에서 실행하는 작은 단위를 말하며 JVM에 의해 관리된다. 프로세스는 적어도 한 개 이상의 스레드가 있으며, Main 스레드 하나 시작하여 스레드를 추가 생성하게 되면 멀티스레드 환경이 된다. 이러한 스레드들은 프로세스의 리소스를 공유하기에 효율적이기도 하지만 잠재적인 문제에 노출될 가능성이 있다.
사용하는 이유
스레드를 사용하는 이유는 벙렬 처리와 동시성 처리를 구현하여 효율성을 높이기 위해 사용된다. 예를 들면 웹 서버에서 여러 클라이언트 요청을 처리하거나, 오래 걸리는 파일 다운로드, UI 업데이트를 동시해 수행하거나 또는 서버에서의 데이터베이스 서버 연결 관리 등의 여러 이유에서 사용된다.
유의할 점
- 동기화 문제 : 여러 스레드가 같은 자원에 접근할 경우 데이터 일관성을 유지해야 한다.
- 데드락 : 두 스레드가 서로의 자원을 기다리며 멈추는 상태
- 컨텍스트 스위칭 비용 : 스레드가 많아질수록 CPU가 작업 전환에 소비하는 비용이 증가한다.
- 디버깅 복잡성 : 멀티스레드 환경에서는 오류와 버그가 발생하기 쉽고, 디버깅이 어렵다.
주요 메서드
start() | 스레드를 실행 상태로 변경하여 run() 메서드를 호출한다. |
run() | 스레드가 실행할 코드를 정의하는 메서드 |
sleep(ms) | 지정된 시간(ms) 동안 스레드를 일시 정지한다. |
join() | 다른 스레드가 작업을 완료할 때까지 대기한다. |
yieId() | 현재 실행 중인 스레드가 실행 중인 상태를 포기하고 다른 스레드에 실행 기회를 제공한다. |
https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html
다른 메서드들을 사용하고 싶다면 공식문서를 보도록하자.
사용 예시
구현방법은 Thread 클래스를 상속 받아 스레드를 생성하거나, Runnable 인터페이스를 활용하여 구현한다.
Thread 상속받기
public class MyThread extends Thread{
int num;
public MyThread(int num) {
this.num = num;
}
public void run(){ // 스레드를 상속하면 메서드를 구현해야 한다.
System.out.println(num + " MyThread run.");
try {
Thread.sleep(1000);
}catch (Exception e){
}
System.out.println(num + " MyThread end.");
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyThread myThread = new MyThread(i);
myThread.start();
}
}
}
이코드는 Thread 클래스를 상속 받아 각 스레드를 생성하는 코드이다. 각 스레드가 가지는 num 값과 "MyThread run." 을 출력하고 1초동안 일시 정지한 후 "MyThread end"를 출력한다.
main 메서드에서 for 루프를 사용해 0부터 9까지 반복하며 각 스레드마다 i값을 전달하고, start()메서드를 호출하여 각 메서드를 실행한다.
start()는 run() 메서드를 비동기적으로 호출한다. 즉 여러 스레드가 동시에 실행된다.
실행 결과
6 MyThread run.
9 MyThread run.
1 MyThread run.
0 MyThread run.
7 MyThread run.
2 MyThread run.
4 MyThread run.
3 MyThread run.
8 MyThread run.
5 MyThread run.
9 MyThread end.
6 MyThread end.
7 MyThread end.
2 MyThread end.
1 MyThread end.
4 MyThread end.
0 MyThread end.
5 MyThread end.
8 MyThread end.
3 MyThread end.
만약 스레드를 사용하지 않았더라면 run을 출력 -> 1초 sleep -> end를 출력 을 10번 반복 했을 것이다. 하지만 스레드를 이용하여 병렬처리를 했기 때문에 스레드를 모두 거의 동시에 실행하였다. 특히 for문의 간단한 계산 작업이 걸리는 시간은 매우 짧아서 보통 0.00000X초 수준이기 때문에 모든 스레드가 1초 sleep을 완료하기 전에 run을 출력한 후, end를 출력한 것이다.
num값이 순서대로 출력되지 않은 이유는 기본적으로 스레드는 실행 순서를 보장하지 않는다고 한다. 순서를 보장하려면 join()메서드를 사용하거나, synchronized 블록을 사용하거나, ExecutorService를 사용하는 방법 등이 있다.
Runnable 인터페이스 구현
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread run");
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable); // thread에 Runnable 전달
thread.start();
}
}
Runnable 인터페이스를 사용하여 스레드를 생성하고 실행한다. 다른 점이 있따면 Thread 클래스의 생성자에 Runnable 객체를 전달하고, Thread 클래스는 Runnable 객체를 사용해 run() 메서드를 호출한다.
각각의 장단점
Thread를 상속받아 사용
Thread 클래스를 상속받아 run() 메서드를 오버라이드하고 start() 메서드를 호출하여 스레드를 실행하는데, 단순하게 바로 상속받아서 스레드를 생성하고 실행할 수 있으며 직관적이라 할 수 있다, 하지만 단일 상속제한 즉 여러 개의 클래스를 상속받을 수 없기 때문에 이미 다른 클래스를 상속받은 클래스는 Thread를 상속 받을 수 없다.
Runnable 인터페이스 사용
Runnable은 이 인터페이스를 구현하여 run() 메서드를 정의한 후, Thread 객체에 Runnable을 전달하여 스레드를 실행한다. 이것을 사용하면 인터페이스이기 때문에 다중 상속이 가능하고, 특히 ExcutorService와 함께 사용할 수 있기 떄문에 효율적이고 유연성이 높다. 또, run() 메서드를 별도의 객체로 분리할 수 있어 테스트를할 수 있다는 장점이 있다. 하지만 Thread와 Runnable을 별도로 사용해야 하므로 코드가 조금 더 복잡해 질 수 있다.
간단한 작업에는 Thread를 사용하는게 유용할 수 있고, 유연한 멀티스레딩 작업이나 여러 클래스 상속이 필요한 경우에는 Runnable을 사용하여 구현하고 Thread 객체로 실행하는 것이 좋다고 한다.
스레드는 자바 개발에서 중요한 도구로, 성능 최적화, 비동기 처리, 멀티스레드 환경에서의 동기화 문제 해결 등 여러 측면에서 유용하다는 것, 적절히 활용하여 높은 트래픽을 처리할 수 있는 웹 애플리케이션이나 서비스에서 좋은 무기가 될 수 있지 않을까?
'Java' 카테고리의 다른 글
[Java] Optinal 클래스 사용해보기 (0) | 2024.03.13 |
---|---|
[Java] Collection Framework 총 정리 (1) | 2023.12.26 |
[Java] Interface 정리(2) (0) | 2023.07.18 |
[Java] InterFace 정리(1) (0) | 2023.07.17 |
[Java] 자바 상속 정리 (0) | 2023.07.14 |