Thread
Thread
Thread
- 프로그램 내에서 독립적으로 실행되는 하나의 작업 단위이다.
- 싱글 쓰레드는 한번에 하나의 작업만 처리하지만, 멀티 쓰레드는 여러 작업을 동시에 처리 할 수 있다.
- 멀티 쓰레드를 활용하면 여러 작업을 병렬로 수행할 수 있어 처리 성능을 향상시킬 수 있다.
싱글 쓰레드 (Single Thread)
- 한 명의 일꾼이 작업을 처리하는 것과 같다.
- 한명의 일꾼이기 때문에 여러 개의 작업이 있다면 순차적으로 처리한다.
- main() 메서드는 프로그램 시작과 동시에 생성되는 하나의 쓰레드이다.
싱글 쓰레드 예제
System.out.println("::: main 쓰레드 시작");
String threadName = Thread.currentThread().getName();
// 하나의 작업 : 숫자 0 ~ 9 까지 출력
for(int i=0; i< 10;i++){
System.out.println("threadName = " + threadName + " - " + i );
try {
Thread.sleep(500); // 0.5초 딜레이
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 순차적으로 실행
for(int i=0; i< 10;i++){
System.out.println("threadName = " + threadName + " - " + i );
try {
Thread.sleep(500); // 0.5초 딜레이
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("::: 작업 끝 :::");
}
// 결과값
// threadName = main - 0
// threadName = main - 1
// threadName = main - 2
// threadName = main - 3
// threadName = main - 4
// threadName = main - 5
// threadName = main - 6
// threadName = main - 7
// threadName = main - 8
// threadName = main - 9
// ::: 작업 끝 :::
멀티 쓰레드 (Multi Thread)
- 작업을 처리할 수 있는 여러 명의 일꾼을 의미
- 멀티 쓰레드를 이용하면 병렬(동시)에 처리할 수 있다.
- Thread 클래스를 상속받아 쓰레드를 구현할 수 있다.
Thread.run()메서드를 오버라이드 해서쓰레드가 수행할 작업을 정의할 수 있습니다.start()메서드를 호출하면 새로운 쓰레드가 생성되고run()의 작업 내용이 실행됩니다.
멀티쓰레드 예제
// Mythread class
public class Mythread extends Thread {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println("현재 시작된 쓰레드");
for (int i = 0; i < 10; i++) {
System.out.println("현재 쓰레드 : " + threadName + " - " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("종료된 쓰레드 : " + threadName);
}
}
System.out.println("::: main 쓰레드 시작");
Mythread thread0 = new Mythread();
Mythread thread1 = new Mythread();
// 1. thread0 시작
System.out.println("thread0 시작");
thread0.start();
// 2. thread1 시작
System.out.println("thread1 시작");
thread1.start();
System.out.println("::: main 쓰레드 종료");
// 결과값
// ::: main 쓰레드 시작
// thread0 시작
// thread1 시작
// ::: main 쓰레드 종료
// 현재 시작된 쓰레드
// 현재 시작된 쓰레드
// 현재 쓰레드 : Thread-1 - 0
// 현재 쓰레드 : Thread-0 - 0
// 현재 쓰레드 : Thread-0 - 1
// 현재 쓰레드 : Thread-1 - 1
// 현재 쓰레드 : Thread-0 - 2
// 현재 쓰레드 : Thread-1 - 2
// 현재 쓰레드 : Thread-1 - 3
// 현재 쓰레드 : Thread-0 - 3
// 현재 쓰레드 : Thread-1 - 4
// 현재 쓰레드 : Thread-0 - 4
// 현재 쓰레드 : Thread-1 - 5
// 현재 쓰레드 : Thread-0 - 5
// 현재 쓰레드 : Thread-0 - 6
// 현재 쓰레드 : Thread-1 - 6
// 현재 쓰레드 : Thread-0 - 7
// 현재 쓰레드 : Thread-1 - 7
// 현재 쓰레드 : Thread-0 - 8
// 현재 쓰레드 : Thread-1 - 8
// 현재 쓰레드 : Thread-0 - 9
// 현재 쓰레드 : Thread-1 - 9
// 종료된 쓰레드 : Thread-1
// 종료된 쓰레드 : Thread-0
Join()
- 특정 쓰레드가 끝날 때까지 기다리게 하는 메서드이다.
Join() 예제
System.out.println("::: main 쓰레드 시작");
Mythread thread0 = new Mythread();
Mythread thread1 = new Mythread();
long startTime = System.currentTimeMillis();
// 1. thread0 시작
System.out.println("thread0 시작");
thread0.start();
// 2. thread1 시작
System.out.println("thread1 시작");
thread1.start();
// main 쓰레드를 대기 시키기
try {
thread0.join();
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
long totleTime = endTime - startTime;
System.out.println("totleTime = " + totleTime + "ms");
System.out.println("::: main 쓰레드 종료");
// 결과값
// ::: main 쓰레드 시작
// thread0 시작
// thread1 시작
// 현재 시작된 쓰레드
// 현재 시작된 쓰레드
// 현재 쓰레드 : Thread-0 - 0
// 현재 쓰레드 : Thread-1 - 0
// 현재 쓰레드 : Thread-1 - 1
// 현재 쓰레드 : Thread-0 - 1
// 현재 쓰레드 : Thread-1 - 2
// 현재 쓰레드 : Thread-0 - 2
// 현재 쓰레드 : Thread-0 - 3
// 현재 쓰레드 : Thread-1 - 3
// 현재 쓰레드 : Thread-1 - 4
// 현재 쓰레드 : Thread-0 - 4
// 현재 쓰레드 : Thread-1 - 5
// 현재 쓰레드 : Thread-0 - 5
// 현재 쓰레드 : Thread-1 - 6
// 현재 쓰레드 : Thread-0 - 6
// 현재 쓰레드 : Thread-1 - 7
// 현재 쓰레드 : Thread-0 - 7
// 현재 쓰레드 : Thread-1 - 8
// 현재 쓰레드 : Thread-0 - 8
// 현재 쓰레드 : Thread-1 - 9
// 현재 쓰레드 : Thread-0 - 9
// 종료된 쓰레드 : Thread-1
// 종료된 쓰레드 : Thread-0
// totleTime = 5067ms
// ::: main 쓰레드 종료
Runnable 인터페이스
- 함수형 인터페이스
- 쓰레드를 구현하기 위한 템플릿에 해당한다
- 왜 쓰는가? : Thread를 상속받으면 다중 상속이 안되기때문에 일반적으로 Runnable을 사용한다.
Runnable 예제
// MyNewClass class
public void printMessange(){
System.out.println("MyNewClass의 기능을 실행합니다.");
}
// Myrannable class
public class Myrannable extends MyNewClass implements Runnable{
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println("현재 시작된 쓰레드");
for (int i = 0; i < 10; i++) {
System.out.println("현재 쓰레드 : " + threadName + " - " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("종료된 쓰레드 : " + threadName);
}
}
// Main class
Myrannable myTask = new Myrannable();
//기능을 확장해서 사용
myTask.printMessange();
Thread thread0 = new Thread(myTask);
Thread thread1 = new Thread(myTask);
thread0.start();
thread1.start();
// 결과값
// MyNewClass의 기능을 실행합니다.
// 현재 시작된 쓰레드
// 현재 시작된 쓰레드
// 현재 쓰레드 : Thread-0 - 0
// 현재 쓰레드 : Thread-1 - 0
// 현재 쓰레드 : Thread-0 - 1
// 현재 쓰레드 : Thread-1 - 1
// 현재 쓰레드 : Thread-1 - 2
// 현재 쓰레드 : Thread-0 - 2
// 현재 쓰레드 : Thread-1 - 3
// 현재 쓰레드 : Thread-0 - 3
// 현재 쓰레드 : Thread-1 - 4
// 현재 쓰레드 : Thread-0 - 4
// 현재 쓰레드 : Thread-1 - 5
// 현재 쓰레드 : Thread-0 - 5
// 현재 쓰레드 : Thread-1 - 6
// 현재 쓰레드 : Thread-0 - 6
// 현재 쓰레드 : Thread-1 - 7
// 현재 쓰레드 : Thread-0 - 7
// 현재 쓰레드 : Thread-0 - 8
// 현재 쓰레드 : Thread-1 - 8
// 현재 쓰레드 : Thread-0 - 9
// 현재 쓰레드 : Thread-1 - 9
// 종료된 쓰레드 : Thread-1
// 종료된 쓰레드 : Thread-0
궁금증
- 왜 조인을 할때 InterruptedException 예외를 처리해야하나요?
- join() 메서드는 다른 스레드가 종료될 때 까지 현재 스레드를 기다리게 하는데, 현재 스레드가 interrupt(중단)되면 JVM은 InterruptedException를 던져서 알려준다.
- 그렇기 때문에 예외처리를 강제하고, 개발자에게 “지금 스레드가 중단되었는데 어떻게 처리할래?”라고 묻는 것이다.
- 같은 객체를 thread에 넣어 join을 시키면 어떻게 될까?
- 서로 다른 객체를 사용하게되면 독립 실행이기 때문에 정상적으로 작동한다.
- 같은 객체를 사용하게되면 공유 자원을 사용하게 되어 충돌이 일어나 이상한 값이 나오게 된다.
- 위의 상황은 오류는 같은 객체의 필드를 동시에 수정하므로 race condition 발생한다.
- 때문에 동기화작업이 필요하다.
느낀점
아직은 활용할 방법이 생각나지 않지만 알고있으면 나중에 써먹을 곳이 생기겠지?