지식조각모음
13. 스레드 본문
반응형
process VS thread
- 프로세스
- 실행 중인 프로그램
- 프로그램을 실행하면 OS로부터 실행에 필요한 자원을 할당받아 프로세스가 된다
- 프로세스 = 데이터 + 메모리 + 스레드
- 스레드
- 프로세스의 자원을 이용해서 실제로 작업을 수행하는 것
- 모든 프로세스는 최소한 하나 이상의 스레드가 존재
멀티스레딩
- 멀티스레드: 둘 이상의 스레드가 동작
- 장점
- CPU 사용률을 향상
- 자원을 보다 효율적으로 사용
- 사용자에 대한 응답성 향상
- 예: 파일을 다운로드 받으면서 채팅하기
- 한 번에 하나의 작업만 가능했다면 불가능함
- 작업이 분리되어 코드가 간결
- 단점
- 동기화 -> 자원 공유
- deadlock
스레드의 구현과 실행
구현 방법
- Thread 클래스 상속
- Runnable 인터페이스 구현
- 일반적으로 사용
-> 두 작업 모두 추상메서드의 run()의 몸통을 채우는 일
구현 및 실행 예제
1. Thread로 구현
class ThreadExample extends Thread {
public void run() {
// 구현
}
}
- Thread 클래스를 상속받아 run()을 오버라이딩 한다
public static void main(String[] args){
ThreadExample thread1 = new ThreadExample();
thread1.start();
}
2. Runnable로 구현
class RunnableExample implements Runnable {
public void run() {
// 구현
}
}
- 인터페이스로 구현하였기 때문에 다른 클래스를 상속 받을 수 있다
- 추상메소드 run()을 구현
public static void main(String[] args){
Runnable r = new RunnableExample();
Thread thread = new Thread(r);
thread.start();
}
- 다만 start() 클래스가 없기 때문에 Runnable 임터페이스를 구현한 클래스의 인스턴스를 생성 한 다음, Thread클래스의 생성자의 매개변수로 넘겨준다.
실행 - start()
start() 메소드를 호출해야 스레드가 실행된다
- 실행: start()를 호출해야 (대기상태로 옮긴 뒤 자신의 차례에)스레드가 실행된다
- 종료: 한 번 실행이 종료된 스레드는 다시 실행할 수 없다
start()와 run()
run()과 start()는 다르다. run()를 호출하는 것은 스레드를 실행시키는 것이 아니라 단순히 클래스에 선언된 메서드를 호출한다. 스레드를 실행시키기 위해서는 start() 메소드를 호출해야 한다.
start()를 호출하면
- 새로운 스레드 생성 -> 이때 필요한 call stack 생성
- 새로운 call stack에서 run()을 호출해서 run()이 스택의 제일 위로 올라가게 한다.
- 호출스택이 2개이므로 순서에 맞게 실행된다
main 스레드
main 메서드의 작업을 수행하는 스레드
프로그램을 실행하기 위해 기본 적으로 하나의 스레드가 필요하고, 그 스레드가 main 메서드를 호출하여 작업을 수행한다.
싱글스레드와 멀티스레드
- 싱글 스레드
- 한 작업이 끝난 후 다른 작업 시작(t1 -> t2)
- 멀티 스레드
- 짧은 시간동안 2개의 스레드를 번걸아가면서 수행 -> 동시에 두 작업이 처리되는 것 같음
- t1과 t2의 작업 수행 완료시간은 거의 같음
- 작업전환(context switching)에 시간이 걸리므로 오히려 싱글 스레드보다 작업시간이 더 소요될 수도 있다
소요시간
멀티스레드 일 때 오히려 싱글 스레드보다 작업시간이 더 소요되는 경우가 있다. 그 이유는
- context switching
- 스레드가 서로 같은 자원을 사용하면 대기시간이 발생한다.
- 예를 들어 멀티스레드로 화면에 글자를 출력할 때 화면(console)이라는 자원을 두고 두 스레드가 경쟁한다.
- 그러므로 서로 다른 자원을 사용하는 경우엔 하나의 스레드(t1)에서 idle이 발생하는 경우 다른 스레드(t2)가 작업을 할 수 있어서 효율적이다
우선순위
- 우선순위: 스레드가 가지고 있는 멤버 변수. 이 값에 따라 스레드가 얻는 실행시간이 달라짐
- 예 : 사용자에게 빨리 반응해야 하는 작업의 우선순위가 더 높아야 함
특징
- 범위: 1~10 (main 스레드 우선순위 5)
- 숫자가 클수록 우선순위가 높다
- 스레드의 우선순위는 스레드를 생성한 스레드로부터 상속받는다
- 스레드를 실행하기 전에만 변경할 수 있음
스레드 그룹
- 스레드 그룹: 스레드를 폴더처럼 묶어서 관리 가능
특징
- 모든 스레드는 반드시 스레드 그룹에 포함되어야 한다
- 자신이 속한 그룹 혹은 하위 스레드 그룹만 변경 가능
- 별도로 그룹지정을 하지 않으면 기본적으로 자신을 생성한 스레드와 같은 그룹에 속한다
예
자바 어플리케이션이 실행되면, JVM은 main과 system이라는 스레드 그룹을 만든다.
- main 스레드 그룹
- main 스레드 속함
- system 스레드 그룹
- Finalizer 스레드 속함(GC 수행)
데몬 스레드
일반 스레드의 작업을 돕는 보조적인 스레드. 일반 스레드가 모두 종료되면 데몬 스레드 강제 종료.
- 예: 가비지 컬렉터, 워드프로세서 자동저장, 화면자동갱신 등
스레드의 상태
| 상태 | 설명 |
|---|---|
| NEW | 스레드 생성, start() 호출 전 |
| RUNNABLE | 실행 중 또는 실행 가능한 상태 |
| BLOCKED | 일시정지된 상태 |
| WAITING, TIMED_WAITING | 종료는 아니지만 실행가능하지 않은 상태(unrunnable) |
| TERMINATED | 작업 종료 |
- 스레드 생성(NEW)후 start()가 호출되면 실행대기열에 저장되어 차례를 기다린다
- 실행 대기열: Queue 구조
- NEW 상태의 스레드가 start()가 호출되면 RUNNABLE로 변경된다
- 실행 대기상태에서 실행 상태가 된다
- 실행 중에 아래의 특정 상황이 되면 다시 실행 대기 상태가 왼다
- 주어진 실행시간이 되거나
- yield()를 만나는 경우
- 실행 중에 아래의 특정 상황이 되면 일시정지상태(BLOCKED)가 된다
- suspend(), sleep(), wait(), join(), I/O block
- 4번 상태에서 아래의 특정 상황이 되면 RUNNABLE로 상태가 변경된다
- 지정된 일시정지시간 만료(time-out)
- notify(), interrupt() 호출
- 다시 실행대기열에 저장
- 실행을 모두 마치거나 stop()이 호출되면 스레드는 소멸한다(TERMINATED)
스레드 스케줄링
sleep()
지정된 시간동안 스레드를 멈춘다.
try {
Thread.sleep(1, 500000);
} catch(InterruptedException e) {}
interrupt()
스레드 작업 중지 요청. 요청만 할 뿐 강제종료는 안된다. 단순히 상태를 변경한다.
public class Ex13_9 {
public static void main(String[] args) {
ThreadEx9_1 th1 = new ThreadEx9_1();
th1.start();
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요");
System.out.println("입력하신 값은 " + input + "입니다.");
th1.interrupt(); // interrupted 상태를 true로 변경한다.
System.out.println("isInterrupted(): " + th1.isInterrupted());
}
}
class ThreadEx9_1 extends Thread {
public void run() {
int i = 10;
while (i != 0 && !isInterrupted()) { // interrupted의 상태가 ture가 되면 while문은 종료된다
System.out.println(i--);
for (long x = 0; x < 2500000000L; x++) {
}
}
System.out.println("카운트가 종료되었습니다.");
}
}
suspend(), resume(), stop()
- suspend(): sleep()처럼 스레드를 멈추게 한다
- 교착상태를 일으키기 쉬우므로 사용을 권장하지 않음
- deprecated 됨
- resume(): suspend()에 의해 멈춰진 스레드를 다시 실행대기 상태로 변경
- stop(): 호출 즉시 스레드 종료
- 교착상태를 일으키기 쉬우므로 사용을 권장하지 않음
- deprecated 됨
join(), yield()
- join()
- 다른 스레드의 작업을 기다림
- 작업 중에 다른 스레드의 작업이 먼저 수행되어야 할 때 사용
- interrupt()에 의해 대기상태에서 벗어날수 있음
- yield()
- 자신에게 주어진 실행시간을 다른 스레드에게 양보한다
스레드의 동기화(Synchronization)
여러 스레드가 같은 프로세스 내의 자원을 공유하면 서로의 작업에 영향을 주는 경우 발생. 이를 방지하기 위해 임계 영역(critical section) 과 잠금(lock) 개념을 도입.
- 스레드의 동기화: 한 스레드가 진행 중인 작업을 다른 스레드가 간섭하지 못하도록 막는 것. 각 스레드들이 수행되는 시점을 조절
임계 영역
공유 데이터를 사용하는 코드 영역. 독점을 보장해줘야 하는 영역
각 프로세스는 자신의 임계 구역에 진입하려면 진업허가를 요청해야 함
- 입장 구역(entry section)
- 임계 구역 진입
- 퇴장 구역(exit section)
do {
wait(mutex); // entry section
// critical section
signal(mutext); // exit section
// 나머지 구역(remainder section)
}반응형
'책 > 자바의 정석' 카테고리의 다른 글
| 15. 입출력 (0) | 2022.04.09 |
|---|---|
| 14. 람다와 스트림 (2) | 2022.04.09 |
| 12. 제네릭스, 열거형, 애너테이션 (0) | 2022.03.13 |
| Comparator와 Comparable (1) | 2022.03.10 |
| 11. 컬렉션 프레임웍 (0) | 2022.03.06 |