12.1. 멀티 스레드 개념
12.1.1 프로세스와 스레드
운영체제에서는 실행 중인 하나의 애플리케이션을 프로세스(process)라고 부른다.
사용자가 애플리케이션을 실행하면 운영체제로부터 실행에 필요한 메모리르 할당받아 애플리케이션의 코드를 실행하는데 이것이 프로세스이다.
하나의 애플리케이션은 다중 프로세스를 만들기도 하는데, 예를 들어 Chrome 브라우저를 두 개 실행했다면 두 개의 Chrome 프로세스가 생성된 것이다.
멀티 태스킹(multi tasking)은 두 가지 이상의 작업을 동시에 처리하는 것을 말하는데, 운영체제는 멀티 태스킹을 할 수 있도록 CPU 및 메모리 자원을 프로세스마다 적절히 할당해주고, 병렬로 실행시킨다.
예를 들어 워드로 문서 작업을 하면서 동시에 윈도우 미디어 플레이어로 음악을 들ㅇ르 수 있다.
멀티 태스킹은 꼭 멀티 프로세스를 뜻하지 않는다.
한 프로세스 내에서 멀티 태스킹을 할 수 있도록 만들어진 애플리케이션도 있다.
대표적인 것들이 미디어 플레이어와 메신저이다.
미디어 플레이어는 동영상 재생과 음악 재생이라는 두가지 작업ㅇ르 동시에 처리하고, 메신저는 채팅 기능을 제공하면서 동시에 팡리 전송 기능을 수행하기도 한다.
어떻게 하나의 프로세스가 두 가지 이상의 작업을 처리할 수 있을까?
그 비밀은 바로 멀티 스레디(multi thread)에 있다.
스레드(thread)는 사전적 의미로 한 가닥의 실이라는 뜻인데, 한가지 작업ㅇ르 실행하기 위해 순차적으로 실행할 코드를 실처럼 이어놓았다고 해서 유래된 이름이다.
하나의 스레드는 하나의 코드 실행 흐름이기 때문에 한 프로세스 내에 스레드가 두 개라면 두 개의 코드 실행 흐름이 생긴다는 의미이다.
멀티 프로세스가 애플리케이션 단위의 멀티 태스킹이라면, 멀티 스레드는 애플리케이션 내부에서 멀티 태스킹이라고 볼 수 있다.
멀티 프로세스들은 운영체제에서 할당받은 자신의 메모리를 가지고 실행하기 때문에 서로 독립적이다.
따라서 하나의 프로세스에서 오류가 발생해도 다른 프로세스에게 영향을 미치지 않는다.
멀티 스레드는 하나의 프로세스 내부에 생성되기 때문에 하나의 스레드가 예외를 발생시키면 프로세스 자체가 종료될 수 있어 다른 스레드에게 영향을 미치게된다.
예를 들어 멀티 프로세스인 엑셀과 워드 프로세스를 작성 중 워드에 오류가 생겨 먹통이 되더라도 엑셀은 여전히 사용 가능하다.
그러나 멀티 스레드로 동작하는 메신저의 경우 파일을 전송하는 스레드에서 예외가 발생되면 메신저 프로세스 자체가 종료되기 떄문에 채팅 스레드도 같이 종료된다.
멀티 스레드는 다양한 곳에서 사용된다.
대용량 데이터의 처리 시간을 줄이기 위해 데이터를 분할해서 병렬로 처리하는 곳에서 사용되기도 하고, UI를 가지고 있는 애플리케이션에서 네트워크 통신을 하기 위해 사용되기도 한다.
또한 다수 클라이언트의 요청을 처리하는 서버를 개발할때에도 사용된다.
12.1.2 메인 스레드
- 모든 자바 프로그램은 메인 스레드가 main() 메소드 실행하며 시작
- main() 메소드의 첫 코드부터 아래로 순차적으로 실행
- 실행 종료 조건
• 마지막 코드 실행
• return 문을 만나면
- main 스레드는 작업 스레드들을 만들어 병렬로 코드들 실행
• 멀티 스레드 생성해 멀티 태스킹 수행
-프로세스의 종료
• 싱글 스레드: 메인 스레드가 종료하면 프로세스도 종료
• 멀티 스레드: 실행 중인 스레드가 하나라도 있다면, 프로세스 미종료
모든 자바 애플리케이션은 메인 스레드( main thread) 가 main() 메소드를 실행하면서 시작된다.
메인 스레드는 main() 메소드의 첫 코드부터 아래로 순차적으로 실행하고, main() 메소드의 마지막 코드를 실행하거나 return 문을 만나면 실행이 종료된다.
메인 스레드는 필요에 따라 작업 스레드들을 만들어서 병렬로 코드를 실행할 수 있다.
즉 멀티 스레드를 생성해서 멀티 태스킹을 수행한다.
다음 그림에서 우측의 멀티 스레드 애플리케이션을 보면 메인 스레드가 작업 스레드1을 생성하고 실행한 다음 곧 이어 작업 스레드2를 생성하고 실행한다.
싱글 스레드 애플리케이션에서는 메인 스레드가 종료하면 프로세스도 종료된다.
하지만 멀티 스레드 애플리케이션에서는 실행중인 스레드가 하나라도 있으면 프로세스는 종료되지 않는다.
메인 스레드가 작업 스레드보다 먼저 종료되더라도 작업 스레드가 계속 실행 중이라면 프로세스는 종료되지 않는다.
12.2 작업 스레드 생성과 실행
멀티 스레드로 실행하는 애플리케이션을 개발하려면 먼저 몇 개의 작업을 병렬로 실행할지 결정하고 각 작업별로 스레드를 생성해야 한다.
어떤 자바 애플리케이션이건 메인 스레드는 반드시 존재하기 때문에 메인 작업 이외에 추작적인 병렬 작업의 수만큼 스레드를 생성하면 된다.
자바에서는 작업 스레드도 객체로 생성되기 때문에 클래스가 필요하다.
java.lang.Thread 클래스를 직접 객체화해서 생성해도 되지만, Thread를 상속해서 하위 클래스를 만들어 생성할 수도 있다.
■작업 스레드 생성 방법
- Thread 클래스로부터 직접 생성 (p.579~582)
• Runnable을 매개값으로 갖는 생성자 호출
- Thread 하위 클래스로부터 생성 (p.583~586)
• Thread 클래스 상속 후 run 메소드 재정의 해 스레드가 실행할 코드 작성
12.2.1 Thread 클래스로부터 직접 생성
java.lang.Thread 클래스로 부터 작업 스레드 객체를 직접 생성하려면 다음과 같이 Runnable을 매개값으로 갖는 생성자를 호출해야 한다.
Thread thread = new Thread(Runnable target);
Runnable은 작업 스레드가 실행할 수 있는 코드를 가지고 있는 객체라고 해서 붙여진 이름이다.
Runnable은 인터페이스 타입이기 떄문에 구현 객체를 만들어 대입해야 한다.
Runnable에는 run() 메소드 하나가 정의되어 있는데, 구현 클래스는 run()을 재정의해서 작업 스레드가 실행할 코드를 작성해야 한다.
class Task implments Runnable{
public void run(){
스레드가 실행할 코드
}
}
Runnable은 작업 내용을 가지고 있는 개체이지 실제 스레드는 아니다.
Runnable 구현 객체를 생성한 후 이것을 매개값으로 해서 Thread 생성자를 호출하면 비로소 작업 스레드가 생성된다.
코드를 좀더 절약하기 위해 Thread 생성자를 호출할때 Runnable 익명 객체를 매개값으로 사용할 수 있다.
Runnable 인터페이스는 run() 메소드 하나만 정의 되어 있기 때문에 함수적 인터페이스이다.
따라서 당므과 같이 람다식을 매개값으로 사용할 수 있다.
자바 8부터 지원되기 때문에 자바 7 이전 버전에서는 사용할 수 없다.
람다식은 14장에서 자세히 설명한다.
작업 스레드는 생성되는 즉시 실행되는 것이 아니라, start() 메소드를 호출해야 실행된다.
thread.start();
12.2.2 Thread 하위 클래스로부터 생성
작업 스레드가 생성할 작업을 Runnable로 만들지 않고, Thread의 하위 클래스로 작업 스레드를 정의하면서 작업 내용을 포함 시킬 수도 있다.
Thread 클래스를 상속한 후 run 메소드를 재정의(overrideing)해서 스레드가 실행할 코드를 작성하면 된다.
작업 스레들 클래스로 부터 작업 스레들 객체를 생성하는 방법은 일반적인 객체를 생성하는 방법과 동일하다.
코드를 좀 더 절약하기 위해 다음과 같이 Thread 익명 객체로 작업 스레드 객체를 생성할 수도 있다.
이렇게 작업된 작업 스레드 객체에서 start() 메소드를 호출하면 작업 스레드는 자신의 run() 메소드를 실행하게 된다.
thread.start();
12.2.3 스레드의 이름
스레드는 자신의 이름을 가지고 있다.
스레드의 이름이 큰 역할을 하는 것은 아니지만, 디버깅 할때 어떤 스레드가 어떤 작업을 하는지 조사할 목적으로 가끔 사용된다.
메인 스레드는 "main" 이라는 이름을 가지고 있고, 우리가 직접 생성한 스레드는 자동적으로 "Thread-n" 이라는 이름으로 설정된다.
n은 스레드 번호를 말한다.
Thread-n의 이름을 다른 이름으로 설정하고 싶다면 Thread 클래스의 setName() 메소드로 변경하면 된다.
thread.setname("스레드 이름");
반대로 스레드의 이름을 확인하고 싶다면 getName() 을 이용하면 된다.
thread.getName();
setName()과 getName()은 Thread의 인스턴스 메소드이므로 스레드 객체의 참조가 필요하다.
만약 스레드 객체의 참조를 가지고 있지 않다면 Thread의 정적 메소드인 currentThread()로 코드를 실행하는 현재 스레드의 참조를 얻을 수 있다.
Thread thread = Thread.currentThread();
public class ThreadA extends Thread {
public ThreadA() {
setName("ThreadA");
}
public void run() {
for(int i=0; i<2; i++) {
System.out.println(getName() + "가 출력한 내용");
}
}
}
public class ThreadB extends Thread {
public void run() {
for(int i=0; i<2; i++) {
System.out.println(getName() + "가 출력한 내용");
}
}
}
public class ThreadNameExample {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
System.out.println("프로그램 시작 스레드 이름: " + mainThread.getName());
ThreadA threadA = new ThreadA();
System.out.println("작업 스레드 이름: " + threadA.getName());
threadA.start();
ThreadB threadB = new ThreadB();
System.out.println("작업 스레드 이름: " + threadB.getName());
threadB.start();
}
}
12.3 스레드 우선순위
멀티 스레드는 동시성(Concurrency) 또는 병렬성(Parallelism)으로 실행되기 때문에 이 용어들에 대해 정확히 이해하는 것이 중요하다.
동시성은 멀티 작업을 위해 하나의 코어에서 멀티 스레드가 번갈아가며 실행되는 성질을 말하고,
병렬성은 멀티 작업을 위해 멀티 코어에서 개별 스레드를 동시에 실행하는 성질을 말한다.
싱글 코어 CPU를 이용한 멀티 스레드 작업은 병력적으로 실행되는 것 처럼 보이지만, 사실은 번갈아가며 실행하는 동시성 작업이다.
번갈아 실행하는 것이 워낙 빠르기 때문에 동시에 실행 하는 병렬성처럼 보일 뿐이다.
스레드의 개수가 코어의 수보다 많을 경우, 스레드를 어떤 순서에 의해 동시성으로 실행할 것인가를 결정해야 하는데, 이것을 스레드 스케줄링이라고 한다.
스레드 스케줄링에 의해 스레드들은 아주 짧은 시간에 번갈아가면서 그들의 run() 메소드를 조금씩 실행한다.
자바의 스레드 스케줄링은 우선순위(Priority) 방식과 순환 할당(Round-Robin) 방식을 사용한다.
우선순위 방식은 우선순위가 높은 스레드가 실행 상태를 더 많이 가지도록 스케줄링하는 것을 말한다.
순환할당 방식은 시간할당량( Time Slice)을 정해서 하나의 스레드를 정해진 시간만큼 실행하고 다시 다른 스레드를 실행하는 방식을 말한다.
스레드 우선순위 방식은 스레드 객체에 우선순위 번호를 부여할 수 있기 때문에 개발자가 코드로 제어할 수 있다.
우선순위 방식에서 우선순위는 1부터 10까지 부여되는데 1이 가장 낮은 우선순위이고, 10이 가장 높은 우선순위이다.
우선순위를 부여하지 않으면 모든 스레드들은 기본적으로 5의 우선순의를 할당받는다.
만약 우선 순위를 변경하고 싶다면 Thread 클래스가 제공하는 setPriority() 메소드를 이용하면 된다.
thread.setPriority(우선순위);
우선순위의 매개값으로 1~10까지의 값을 직접 주어도 되지만, 코드의 가독성(이해도)을 높이기 위해 Thread 클래스의 상수를 이용할 수도 있다.
thread.setPriority(Thread.MAX_PRIORITY);
thread.setPriority(Thread.NORM_PRIORITY);
thread.setPriority(Thread.MIN_PRIORITY);
MAX_PRIORITY : 10
NORM_PRIORITY : 5
MIN_PRIORITY : 1
12.4 동기화 메소드와 동기화 블록
12.4.1 공유 객체를 사용할 때의 주의할 점
싱글 스레드 프로그램에서는 한 개의 스레드가 객체를 독차지해서 사용하면 되지만, 멀티 스레드 프로그램에서는 스레드들이 객체를 공유해서 작업해야 하는 경우가 있다.
이 경우 스레드 A를 사용하던 객체가 스레드 B에 의해 상태가 변경될 수 있기 때문에 스레드 A가 의도했던 것과는 다른 결과를 산출할 수도 있다.
이는 마치 여러사람이 계산기를 함께 나눠 쓰는 상황과 같아서 사람 A가 계산기로 작업을 하다가 계산 결괄르 메모리에 저장한 후 잠시 자리를 비웠을떄 사람 B가 계산기를 만져서 앞 사람이 메모리에 저장한 값을 다른 값으로 변경하는 것과 같다.
그럼 다음 사람 A가 돌아와 계산기에 저장된 값을 이용해서 작업을 한다면 결국 A는 엉터리 값을 이용하게 된다.
User1 스레드가 Calculator 객체의 memory필드에 100을 먼저 저장하고, 2초간 일시정지 상태가 된다.
그 동안에 User2 스레드가 memory 필드값을 50으로 변경한다.
2초가 지나고 User1 스레드가 다시 실행 상태가 되어 memory 필드의 값을 출력하면 User2가 저장한 50이 나온다.
public class MainThreadExample {
public static void main(String[] args) {
Calculator calculator = new Calculator();
User1 user1 = new User1();
user1.setCalculator(calculator);
user1.start();
User2 user2 = new User2();
user2.setCalculator(calculator);
user2.start();
}
}
public class Calculator {
private int memory;
public int getMemory() {
return memory;
}
public void setMemory(int memory) {
this.memory = memory;
try {
Thread.sleep(2000);
} catch(InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + ": " + this.memory);
}
}
public class User1 extends Thread {
private Calculator calculator;
public void setCalculator(Calculator calculator) {
this.setName("User1");
this.calculator = calculator;
}
public void run() {
calculator.setMemory(100);
}
}
public class User2 extends Thread {
private Calculator calculator;
public void setCalculator(Calculator calculator) {
this.setName("User2");
this.calculator = calculator;
}
public void run() {
calculator.setMemory(50);
}
}
12.4.2 동기화 메소드 및 동기화 블록
스레드가 사용 중인 객체를 다른 스레드가 변경할 수 없도록 하려면 스레드 작업이 끝날 때까지 객체에 잠금을 걸어서 다른 스레드가 사용할 수 없도록 해야 한다.
멀티 스레드 프로그램에서 단 하나의 스레드만 실행할 수 있는 코드 영역을 임계 영역(critical section)이라고 말한다.
자바는 임계영역을 지정하기 위해 동기화(synchronized) 메소드와 동기화 블록을 제공한다.
스레드가 객체 내부의 동기화 메소드 또는 블록에 들어가면 즉시 객체에 잠금을 걸어 다른 스레드가 임계 영역 코드를 실행하지 못하게 된다.
동기화 메소드
동기화 블록
동기화 블록의 외부 코드들은 여러 스레드가 동시에 실행할 수 있지만, 동기화 블록의 내부 코드는 임계 역역이므로 한 번에 한 스레드만 실행할 수 있고 다른 스레드는 실행할 수 없다.
만약 동기화 메소드와 동기화 블록이 여러 개 있을 경우, 스렏가 이들 중 하나를 실행할 때 다른 스레든느 해당 메소드는 물론이고 다른 동기화 메소드 및 블록도 실행할 수 없다.
하지만 일반 메소드는 실행이 가능하다.
public class Calculator {
private int memory;
public int getMemory() {
return memory;
}
public synchronized void setMemory(int memory) {
this.memory = memory;
try {
Thread.sleep(2000);
} catch(InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + ": " + this.memory);
}
}
12.5 스레드 상태
스레드 객체를 생성하고, start() 메소드를 호출하면 곧바로 스레드가 실행되는 것처럼 보이지만 사실은 실행 대기 상태가 된다.
실행 대기 상태란 아직 스케줄링이 되지 않아서 실행을 기다리고 있는 상태를 말한다.
실행 대기 상태에 있는 스레드 중에서 스레드 스케줄링으로 선택된 스레드가 비로서 CPU를 점유하고 run() 메소드를 실행한다.
이때를 실행(Running) 상태라고 한다.
실행상태의 스레드는 run() 메소드를 모두 실행하기 전에 스레드 스케줄리에 의해 다시 실행 대기 상태로 돌아갈 수 있다.
그리고 실행 대기 상태에 있는 다른 스레드가 선택되어 실행 상태가 된다.
이렇게 스레드는 실행 대기 상태와 실행 상태를 번갈아가면서 자신의 run() 메소드를 조금씩 실행한다.
실행 상태에서 run() 메소드가 종료되면, 더이상 실행할 코드가 없기 때문에 스레드의 실행은 멈추게 된다.
이 상태를 종료상태 라고 한다.
경우에 따라서 스레드는 실행 상태에서 실행 대기 상태로 가지 않을 수도 있다.
실행상태에서 일시정지 상태로 가기도 하는데, 일시 정지 상태는 스레드가 실행할 수 없는 상태이다.
일시정지 상태는 WAITING, TIMED_WAITING, BLOCKED가 있는데 일시정지 상태가 되는 이유는 나중에 설명하도록 하고, 스레드가 다시 실행항태로 가기 위해서는 일시정지 상태에서 실행 대기 상태로 가야 한다는 것만 알아두자.
이러한 스레드의 상태를 코드에서 확인할 수 있도록 하기 위해 자바 5부터 Thread 클래스에 getState() 메소드가 추가되었다.
getState() 메소드는 다음 표처럼 스레드 상태에 따라서 Thread.State 열거 상수를 리턴한다.
12.6 스레드 상태 제어
- 실행 중인 스레드의 상태를 변경하는 것
- 상태 변화를 가져오는 메소드의 종류 (취소선 가진 메소드는 사용 X)
사용자는 미디어 플레이어에서 동영상을 보다가 일시 정지시킬 수도 있고, 종료시킬 수도 있다.
일시 정지는 조금 후 다시 동영상을 보겠다는 의미이므로 미디어 플레이어는 동영상 스레드를 일시 정지 상태로 만들어야 한다.
그리고 종료는 더 이상 이 영상을 보지 않겠다는 의미이므로 미디어 플레이어는 스레드를 종료 상태로 만들어야 한다.
이와 같이 실행중인 스레드의 상태를 변경하는 것을 스레드 상태 제어 라고 한다.
위 그림에서 취소선을 가진 메소드는 스레드의 안전성을 해친다고 하여 더 이상 사용하지 않도록 권장된 Deprecated 메소드들이다.
12.6.1 주어진 시간동안 일시정지( sleep() )
실행 중인 스레드를 일정 시간동안 멈추게 하고 싶다면 Thread 클래스의 정적 메소드인 sleep()을 사용하면 된다.
Thread.sleep() 메소드를 호출한 스레드는 주어진 시간 동안 일시 정지 상태가 되고, 다시 실행 대기 상태로 돌아간다.
try{
Thread.sleep(1000);
}catch(InterruptedException e){
//interrupt() 메소드가 호출되면 실행
}
매개값에는 얼마동안 일시 정지 시킬 것인지에 대한 밀리세컨드(1/1000) 단위로 시간을 주면 된다.
위와 같이 1000을 넣으면 스레드는 1초가 경과할 동안 일시 정지 상태로 있게 된다.
일시 정지 상태에서 주어진 시간이 되기 전에 interrupt() 메소드가 호출 되면 InterruptedException 이 발생하기 때문에 예외처리가 필요하다.
12.6.2 다른 스레드에게 실행 양보( yield() )
스레드가 처리하는 작업은 반복적인 실행을 위해 for문이나 while문을 포함하는 경우가 많다.
가끔은 이 반복문들이 무의미한 반복을 하는 경우가 있다.
public void run(){
while(true){
if(work){
System.out.println("Thread A 작업 내용");
}
}
}
스레드가 시작되어 run() 메소드를 실행하면 while(true){ } 블록을 무한 반복 실행한다.
만약 work의 값이 false 이라면 그리고 work의 값이 false에서 true로 변경되는 시점이 불명확하다면,
while문은 어떠한 실행 문도 실행하지 않고 무의미한 반복을 한다.
이것보다는 다른 스레드에게 실행을 양보하고 자신은 실행 대기 상태로 가는 것이 전체 프로그램 성능에 도움이 된다.
이런 기능을 위해서 스레드는 yield() 메소드를 제공하고 있다.
yield() 메소드를 호출한 스레드는 실행 대기 상태로 돌아가고 동일한 우선순위 또는 높은 우선순위를 갖는 다른 스레드가 실행 기회를 가질 수 있도록 해준다.
다음 코드는 의미 없는 반복을 줄이기 위해 yield() 메소드를 호출해서 다른 스레드에게 실행 기회를 주도록 해준 것이다.
public void run(){
while(true){
if(work){
System.out.println("Thread A의 작업 내용");
}else{
Thread.yield();
}
}
}
12.6.3 다른 스레드의 종료를 기다림( join() )
스레드는 다른 스레드와 독립적으로 실행하는것이 기본이지만 다른 스레드가 종료될 때까지 기다렸다가 실행해야 하는 경우가 있다.
예를 들어 계산 작업을 하는 스레드가 모든 계산 작업을 마쳤을때 계산 결과값을 받아 이용하는 경우가 이에 해당된다.
이런 경우를 위해 Thread는 join() 메소드를 제공한다.
ThreadA가 ThreadB의 join() 메소드를 호출하면 ThreadA는 ThreadB가 종료할 때까지 일시 정지 상태가 된다.
ThreadB의 run() 메소드가 종료되면 비로소 ThreadA는 일시 정지에서 풀려 다음 코드를 실행하게 된다.
12.6.4 스레드 간 협업( wait(), notify(), nofityAll() )
경우에 따라서는 두 개의 스레드를 교대로 번갈아가며 실행해야 할 경우가 있다.
정확한 교대 작업이 필요할 경우, 자신의 작업이 끝나면 상대방 스레드를 일시 정지 상태에서 풀어주고, 자신을 일시 정지 상태로 만드는 것이다.
이 방법의 핵심은 공유 객체에 있다.
공유 객체는 두 스레드가 작업할 내용을 각각 동기화 메소드를 구분해 놓는다.
한 스레드가 작업을 완료하면 notify() 메소드를 호출해서 일시 정지 상태에 있는 다른 스레드실행 대기 상태로 만들고, 자신은 두 번 작업을 하지 않도록 wait() 메소드를 호출하여 일시정지 상태로 만든다.
다음은 데이터를 저장하는 스레드(생상자 스레드)가 데이터를 저장하면, 데이터를 소비하는 스레드(소비자 스레드)가 데이터를 읽고 처리하는 교대 작업을 구현한 것이다.
생성자 스레드는 소비자 스레드가 읽기 전에 새로운 데이터를 두 번 생성하면 안되고, 소비자 스레드는 생성자 스레드가 새로운 데이터를 생성하기 전에 이전 데이터를 두번 읽어서도 안된다.
구현 방법은 공유 객체에 데이터를 저장할 수 있는 data 필드의 값이 null이면 생상자 스레드를 실행 대기 상태로 만들고, 소비자 스레드를 일시 정지 상태로 만드는 것이다.
반대로 data 필드의 값이 null이 아니면 소비자 스레드를 실행 대기 상태로 만들고, 생상자 스레드를 일시 정지 상태로 만들면 된다.
12.6.5 스레드의 안전한 종료( stop 플래그, interrupt() )
스레드는 자신의 run() 메소드가 모두 실행되면 자동적으로 종료된다.
경우에 따라서는 실행중인 스레드를 즉시 종료할 필요가 있다.
예를 들어 동영상을 끝까지 보지 않고 사용자가 멈춤을 요구할수 있다.
Thread는 스레드를 즉시 종료시키기 위해서 stop() 메소드를 제공하고 있는데, 이 메소드는 deprecated되었다.
stop() 메소드로 스레드를 갑자기 종료하게 되면 스레드가 사용중이던 자원들이 불안전한 상태로 남겨지기 때문이다.
여기서 자원이란 파일, 네트워크 연결 등 을 말한다.
그렇다면 스레드를 즉시 종료시키기 위한 최선의 방법은 무엇일까?
stop 플래그를 이용하는 방법
스레드는 run() 메소드가 끝나면 자동적으로 종료되므로, run() 메소드가 정상적으로 종료되도록 유도하는 것이 최선의 방법이다.
public class XXXThread extens Thread{
private boolean stop; //stop플래그 필드
public void run(){
while(!stop){ //stop이 true가 되면 run()이 종료된다.
스레드가 반복 실행하는 코드
}
//스레드가 사용한 자원 정리
}
}
위 코드에서 stop 필드가 false일 경우에는while문의 조건식이 true가 되어 반본을 실행하지만 stop 필드가 true일 경우에는 while문의 조건식이 false가 되어 while문을 빠져나온다.
그리고 스레드가 사용한 자원을 정리하고 run() 메소드가 끝나게 됨으로써 스레드는 안전하게 종료된다.
interrupt() 메소드를 이용하는 방법
• 스레드가 일시 정지 상태일 경우 InterruptedException 발생 시킴
• 실행대기 또는 실행상태에서는 InterruptedException 발생하지 않음
• 일시 정지 상태로 만들지 않고 while문 빠져 나오는 방법으로도 쓰임
interrupt() 메소드는 스레드가 일시정지 상태에 있을 때 InterruptedException 예외를 발생시키는 역할을 한다.
이것을 이용하면 run() 메소드를 정상 종료시킬 수 있다.
'JAVA > 이것이 자바다' 카테고리의 다른 글
Chapter .15 컬렉션 프레임워크 (0) | 2021.07.29 |
---|---|
Chapter .12-2 멀티 스레드 (0) | 2021.07.27 |
Chapter .11-2 기본 API 클래스 (0) | 2021.07.25 |
Chapter .11-1 기본 API 클래스 (0) | 2021.07.23 |
Chapter .10 예외처리 (0) | 2021.07.22 |