이 글은 유튜브 '자바의 정석 - 기초편'을 보고 정리한 글입니다.
📂content
1. 데몬 스레드(daemon thread)
- 일반 스레드(non-daemon thread)의 작업을 돕는 보조적인 역할을 수행
- 일반 스레드가 모두 종료되면 자동적으로 종료된다.
- 가비지 컬렉터, 자동저장, 화면 자동갱신 등에 사용된다.
- 무한루프와 조건문을 이용해서 실행 후 대기하다가 특정조건이 만족되면 작업을 수행하고 다시 대기하도록 작성한다.
public void run() {
while(true) {
try {
Thread.sleep(3 * 1000); //3초마다 쉼
} catch(InterruptedException e) {}
//autoSave의 값이 true이면 autoSave()를 호출한다.
if(autoSave) {
autoSave();
}
}
}
//3초마다 autoSave 설정이 되어있는지 확인하고 자동저장함.
boolean isDaemon()
스레드가 데몬 스레드인지 확인한다. 데몬 스레드이면 true를 반환
void setDaemon(boolean on)
스레드를 데몬 스레드로 또는 사용자 스레드로 변경
매개변수 on을 true로 지정하면 데몬 스레드가 된다.
* setDaemon(boolean on)은 반드시 start()를 호출하기 전에 실행되어야 한다.
그렇지 않으면 IllegalThreadStateException이 발생한다.
위 코드처럼 무한루프와 조건문을 이용해서 스레드를 만들고, 생성한 스레드를 setDaemon을 이용해서 데몬 스레드로 만든다.
⍟실습
Ex13_7
class Ex13_7 implements Runnable {
static boolean autoSave = false;
public static void main(String[] args) {
Thread t = new Thread(new Ex13_7());
t.setDaemon(true); // 이 부분이 없으면 종료되지 않는다.
t.start();
for(int i=1; i <= 10; i++) {
try{
Thread.sleep(1000);
} catch(InterruptedException e) {}
System.out.println(i);
if(i==5) autoSave = true;
}
System.out.println("프로그램을 종료합니다.");
}
public void run() {
while(true) {
try {
Thread.sleep(3 * 1000); // 3초마다
} catch(InterruptedException e) {}
// autoSave의 값이 true이면 autoSave()를 호출한다.
if(autoSave) autoSave();
}
}
public void autoSave() {
System.out.println("작업파일이 자동저장되었습니다.");
}
}
3초마다 autoSave가 true인지 체크한다. 그리고 true이면 작업 파일을 저장한다는 출력을 찍는다.
코드를 읽어보면 5초에 autosave값을 true로 바꿨다.
그래서 5초 이후부터 3초마다 자동저장을 한다.
- 데몬스레드는 무한루프로 작성이 되어도 일반스레드가 하나도 없다면 자동종료가 된다.
2. 스레드의 상태
상태 | 설명 |
NEW | 스레드가 생성되고 아직 start()가 호출되지 않은 상태 |
RUNNABLE | 실행 중 또는 실행 가능한 상태 |
BLOCKED | 동기화블럭에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태) |
WAITING, TIMED_WAITING | 스레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrunnable) 일시정지상태 TIMED_WAITING은 일시정지시간이 지정된 경우를 의미 |
TERMINATED | 스레드의 작업이 종료된 상태 |
start()를 호출하면 RUNNABLE 상태가 된다. 즉, 바로 실행되는 것이 아니라 줄 서기를 해야 한다.
그러다가 자신의 차례가 되면 실행이 되고 주어진 시간이 다 되면 다시 줄을 선다.
자신의 작업이 다 끝나거나 stop()이 호출되면 스레드가 소멸된다.
suspend() 일시정지, sleep() 잠자기, wait(), join() 기다리기, I/O block
=> 스레드가 일시정지 상태가 된다. (스레드의 대기실, 쉼터)
time-out 시간종료, resume() 재개, notify() wait과 한 쌍, interrupt() 자는 걸 깨운다
=> 일시정지 상태에서 다시 나와서 줄을 선다.
3. 스레드의 실행제어
- 스레드의 실행을 제어할 수 있는 메서드가 제공된다.
메서드 | 설명 |
static void sleep(long millis) static void sleep(long millis, int nanos) |
지정된 시간(천분의 일초 단위)동안 스레드를 일시정지(잠들게)시킨다. 지정한 시간이 지나고 나면, 자동적으로 다시 실행대기상태가 된다. |
void join() void join(long millis) void join(long millis, int nanos) |
지정된 시간동안 스레드가 실행되도록 한다. 지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 스레드로 다시 돌아와 실행을 계속한다. |
void interrupt() | sleep()이나 join()에 의해 일시정지상태인 스레드를 깨워서 실행대기상태로 만든다. 해당 스레드에서는 Interrupted Exception이 발생함으로써 일시정지 상태를 벗어나게 된다. |
void stop() | 스레드를 즉시 종료시킨다. |
void suspend() | 스레드를 일시정지시킨다. resume()을 호출하면 다시 실행대기상태가 된다. |
void resume() | suspend()에 의해 일시정지상태에 있는 스레드를 실행대기상태로 만든다. |
static void yield() | 실행 중에 자신에게 주어진 실행시간을 다른 스레드에게 양보(yield)하고 자신은 실행대기상태가 된다. |
static이 붙은 메서드는 스레드 자기 자신에게만 호출이 가능하다.
예를 들어, interrupt()는 다른 스레드를 깨우는 것인데, static이 붙은 것은 다른 스레드에게 적용할 수 없다.
4. sleep()
- 현재 스레드를 지정된 시간동안 멈추게 한다.
static void sleep(long millis) // 천 분의 일초 단위
// sleep(3000) : 3초 (3 * 1000)
static void sleep(long millis, int nanos) // 천 분의 일초 + 나노초
- 예외처리를 해야 한다. (InterruptedException이 발생하면 깨어남)
try {
Thread.sleep(1, 500000); //스레드를 0.0015초 동안 멈추게 한다.
} catch (InterruptedException e) {}
void delay(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {}
}
sleep()은 예외를 발생시킬 수 있는 메서드라 예외를 필수로 처리해주어야 한다.
thread가 멈추는 경우는 timeup(시간 종료)되거나 interrupted(누가 깨움)인 경우가 있다.
시간이 종료되는 경우는 상관이 없지만 interrupted 되는 경우는 Thread가 예외(InterruptedException)를 던진다.
그래서 try-catch를 만든 것이고 catch문에 딱히 아무 코드가 없는 이유는 문제가 있어서 예외가 발생한 것이 아니라 예외와 try-catch문을 이용해서 잠자는 상태에서 벗어나게 만들어 놓은 것이기 때문이다.
그런데 항상 예외처리를 하는 것이 귀찮아서 메소드를 따로 만든다.
그래서 sleep 쓸 때마다 예외처리 하는 것보다는 메서드를 만들어서 이용한다.
- 특정 스레드를 지정해서 멈추게 하는 것은 불가능하다.
try {
th1.sleep(2000);
} catch(InterruptedException e) {}
th1을 잠자게 하는 것처럼 보이지만 sleep은 다른 스레드를 잠재울 수 없다. 현재 이 코드를 실행하는 스레드가 잠자는 것이다. 그래서 위와 같이 코드를 쓰는 것보다는 아래와 같은 코드를 쓰는 것이 좋다.
위 코드를 돌려도 에러는 안 나지만 오해하게 만든다. 그래야 오해의 여지가 없다.
try {
Thread.sleep(2000);
} catch (InterruptedException e) {}
⍟실습
Ex13_8
class Ex13_8 {
public static void main(String args[]) {
ThreadEx8_1 th1 = new ThreadEx8_1();
ThreadEx8_2 th2 = new ThreadEx8_2();
th1.start(); th2.start();
try {
Thread.sleep(2000);
//th1.sleep(2000); //사용하지 말기
} catch(InterruptedException e) {}
System.out.print("<<main 종료>>");
} // main
}
class ThreadEx8_1 extends Thread {
public void run() {
for(int i=0; i < 300; i++) System.out.print("-");
System.out.print("<<th1 종료>>");
} // run()
}
class ThreadEx8_2 extends Thread {
public void run() {
for(int i=0; i < 300; i++) System.out.print("|");
System.out.print("<<th2 종료>>");
} // run()
}
지금 위 코드는 메인 스레드, th1스레드, th2스레드가 있다.
위 코드에서 th1.sleep(2000)을 해서 th1스레드를 잠자게 하는 것처럼 보이지만 사실은 메인스레드를 재우는 것이다. 그래서 "<<main종료>>"가 2초 뒤인 가장 마지막에 출력이 된다.
저 sleep의 try-catch문을 주석처리하면 "<<main 종료>>"가 가장 먼저 출력된다.
th1.sleep()으로 쓰는 것보다 Thread.sleep()으로 써야 한다.
5. interrupt()
- 대기상태(WAITING)인 스레드를 실행대기 상태(RUNNABLE)로 만든다.
void interrupt()
스레드의 interrupted상태를 false에서 true로 변경
boolean isInterrupted()
스레드의 interrupted상태를 반환
static boolean interrupted()
현재 스레드의 interrupted상태를 알려주고, false로 초기화
public static void main (String[] args) {
ThreadEx13_2 th1 = new ThreadEx13_2();
th1.start();
...
th1.interrupt(); //interrupt()를 호출하면, interrupted상태가 true가 된다.
...
System.out.println("isInterrupted():" + th1.isInterrupted()); //true
}
class Thread { //알기 쉽게 변경한 코드
...
boolean interrupted = false;
...
boolean isInterrupted() {
return interrupted;
}
boolean interrupt() {
interrupted = true;
}
}
interrupted() 값을 초기화해준다. 그래야 interrupt()를 호출할 때 알 수 있기 때문이다. 초기화를 하지 않으면 interrupted라는 변수가 계속 true상태로 있다.
class ThreadEx13_2 extends Thread {
public void run() {
...
while(downloaded && !isInterrupted()) {
//download를 수행한다.
...
}
System.out.println("다운로드가 끝났습니다.");
}//main
}
파일 다운로드 받을 때 취소 버튼이 있어야 한다. 취소 버튼을 누르면 interrupt()가 호출되게 한다.
그래서 이 스레드가 다운로드를 실행하다가 isInterrupted()의 결과가 true인데 !에 의해 false가 된다.
그래서 while문을 빠져나오게 된다.
⍟실습
Ex13_9
import javax.swing.JOptionPane;
class Ex13_9 {
public static void main(String[] args) throws Exception {
ThreadEx9_1 th1 = new ThreadEx9_1();
th1.start();
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
System.out.println("입력하신 값은 " + input + "입니다.");
th1.interrupt(); // interrupt()를 호출하면, interrupted상태가 true가 된다.
System.out.println("isInterrupted():"+ th1.isInterrupted()); // true
}
}
class ThreadEx9_1 extends Thread {//카운트다운하는 스레드
public void run() {
int i = 10;
while(i!=0 && !isInterrupted()) {
System.out.println(i--);
for(long x=0;x<2500000000L;x++); // 시간 지연
}
System.out.println("카운트가 종료되었습니다.");
}
}
//main스레드가 interrupt되었는지 확인
System.out.println("interrupted():" + th1.interrupted()); //false
이 코드를 위 코드에 집어넣어도 결과는 false이다. 그 이유는 th1이라고 써도 interrupted()는 static이기 때문에 자기 자신에게만 적용이 된다. 그래서 interrupt()된 것은 th1이고, main스레드는 interrupt()된 적이 없으므로 true가 아닌 false라는 값이 나오는 것이다. 그래서 사실 위 th1.interrupted()는 Thread.interrupted()가 맞는 것이다.
헷갈리지 말기!!!
출처
'🎥Back > 자바의 정석' 카테고리의 다른 글
[JAVA의 정석]람다식과 함수형 인터페이스 (2) | 2024.03.06 |
---|---|
[JAVA의 정석] 스레드와 관련된 함수 및 동기화 (0) | 2024.03.01 |
[JAVA의 정석]싱글 스레드와 멀티스레드, 스레드의 I/O 블락킹 (0) | 2024.02.29 |
[JAVA의 정석]스레드 (0) | 2024.02.29 |
[JAVA의 정석]Object클래스와 equals() (0) | 2024.02.29 |