데몬 쓰레드(daemon thread)
- 일반 쓰레드(non-daemon thread)의 작업을 돕는 보조적인 역할 수행
- 일반 쓰레드가 모두 종료되면 자동적으로 종료된다.
- GC(Garbage Collector), 자동저장, 화면 자동갱신 등에 사용된다.
- 무한루프와 조건문을 이용해서 실행 후 대기하다가 특정조건이 만족되면 작업을 수행하고 다시 대기하도록 작성한다.
- 데몬 쓰레드는 쓰레드를 생성한 다음 실행하기전에 setDaemon(true)를 호출하기만 하면 된다.
boolean isDaemon( ) | 쓰레드가 데몬쓰레드인지 확인한다. 데몬 쓰레드이면 true를 반환 |
void setDaemon (boolean on) | 쓰레드를 데몬 쓰레드로 또는 사용자 쓰레드로 변경. 매개변수 on을 true로 지정하면 데몬쓰레드가 된다. |
*setDaemon(Boolean on)은 반드시 start()를 호출하기 전에 실행이 안되면 illegalThreadStateException이 발생한다.*
예제) 3초마다 변수 autoSave의 값을 확인해서 그 값이 true면, autoSave( )를 호출하는 일을 무한히 반복하도록 쓰레드를 작성.
public class Ex13_7 implements Runnable { // Runnable 인터페이스를 구현!
static boolean autoSave = false; // autoSave를 false로 초기화
public static void main(String[] args) {
// 일반 쓰레드
Thread t = new Thread(new Ex13_7()); // Thread(Runnable r) Ex13_7은 run()구현해서 들어갈 수 있음
t.setDaemon(true); // 이 부분이 없으면 종료되지 않는다. 실행되기전에 set!
t.start();
for(int i=1; i<=15; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {} // 예외처리
System.out.println(i + " ");
if(i==5) autoSave = true; // i가 5일 때 autoSave가 false -> true
}
System.out.println("프로그램을 종료합니다.");
}
// Daemon 쓰레드(일반쓰레드가 하나도 없을 때 자동종료)
public void run() {
while(true) { // 무한 반복문
try {
Thread.sleep(3*1000); // 3초마다
} catch(Exception e) { }
// autoSave의 값이 true이면 autoSave( )를 호출
if(autoSave) autoSave();
}
}
public void autoSave() {
System.out.println("작업파일이 자동저장되었습니다.");
}
}
<출력값>
1 2 3 4 5 작업파일이 자동저장되었습니다.
6 7 8 작업파일이 자동저장되었습니다.
9 10 11 작업파일이 자동저장되었습니다.
12 13 14 작업파일이 자동저장되었습니다.
15 프로그램을 종료합니다.
쓰레드의 실행제어
- 쓰레드의 스케줄링을 잘하기 위해서는 쓰레드의 상태와 관련 메서드를 잘 알아야 함
- 효율적인 멀티쓰레드 프로그램을 만들기 위해서는 보다 정교한 스케줄링을 통해 프로세스에게 주어진 자원과 시간을 낭비없이 잘 사용하도록 프로그래밍 해야함
*쓰레드의 스케줄링과 관련된 메서드*
메서드 | 설명 |
static void sleep(long millis) static void sleep(long millis, int nanos) |
지정된 시간(천분의 일초 단위)동안 쓰레드를 일시정지시킨다.(잠들게) 지정한 시간이 지나고 나면, 자동적으로 다시 실행대기상태가 된다. ex) 5초 -> 5000 |
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 void sleep(), static void yield( ) : 쓰레드 자기자신에게만 호출가능(다른 쓰레드한테 적용 불가능)
쓰레드의 상태
상태 | 설명 |
NEW | 쓰레드가 생성되고 아직 start( )가 호출되지 않은 상태 |
RUNNABLE | 실행 중 또는 실행 가능한 상태 |
BLOCKED | 동기화블럭에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태) |
WAITING, TIMED_WAITING | 쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrunnable) 일시정지상태, TIMED_WAITING은 일시정지시간이 지정된 경우를 의미 |
TERMINATED | 쓰레드의 작업이 종료된 상태 |
쓰레드의 상태가 변화되는 과정
1) 쓰레드를 생성하고 start( )를 호출하면 바로 실행되는 것이 아니라 실행대기열에 저장되어 자신의 차례가 될 때까지 기다려야 한다. 실행대기열은 Queue와 같은 구조로 먼저 실행대기 열에 들어온 쓰레드가 먼저 실행된다. |
2) 실행대기상태에 있다가 자신의 차례가 되면 실행상태가 된다. |
3) 주어진 실행시간이 다되거나 yield( )를 만나면 다시 실행대기상태가 되고 다음 차례의 쓰레드가 실행상태가 된다. |
4) 실행 중에 suspend( ), sleep( ), wait( ), join( ), I/O block에 의해 일시정지상태가 될 수 있다. |
5) 지정된 일시정지시간이 다되거나(time-out), notify( ), resume( ), interrupt( )가 호출되면 일시정지상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다리게 된다. |
6) 실행을 모두 마치거나 stop( )이 호출되면 쓰레드는 소멸된다. |
Sleep( ) - 일정시간동안 쓰레드를 멈추게 한다.
- 현재 쓰레드를 지정된 시간동안 멈추게 한다.
- static 메서드, static 메서드는 자기자신을 호출함(잠잘 시간)
- sleep( )에 의해 일시정지 상태가 된 쓰레드는 지정된 시간이 다 되거나 interrupt()가 호출되면(InterruptedException 발생, 예외처리 해줘야함), 잠에서 꺠어나 실행되기 상태가 된다.
static void sleep(long millis) // 천분의 일초 단위
static void sleep(long millis, int nanos) // 천분의 일초 + 나노초
-> 3초 (3*1000)
예제)
public class Ex13_8 {
public static void main(String[] args) {
ThreadEx8_1 th1 = new ThreadEx8_1(); // Thread를 상속받은 ThreadEx8_1객체 생성
ThreadEx8_2 th2 = new ThreadEx8_2();
th1.start();
th2.start();
try {
Thread.sleep(2000);
} catch(InterruptedException e) {}
System.out.println("<<main 종료>>");
} // main
}
class ThreadEx8_1 extends Thread {
public void run() {
for(int i=0; i<100; i++)
System.out.print("-");
System.out.print("<<th1 종료>>");
}// run()
}
class ThreadEx8_2 extends Thread {
public void run() {
for(int i=0; i<100; i++)
System.out.println("|");
System.out.println("<<th2 종료>>");
}// run()
}
<출력값>
---||||||||||||||||||||||||||||||||||-----||||-------|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||-------------------------------------------------------------------------------------<<th1 종료>>|<<th2 종료>>
<<main 종료>>
-> 예제에서 쓰레드가 3개 있음(main 쓰레드, th1 쓰레드, th2 쓰레드) main 쓰레드에서 sleep( )을 호출하기 때문에 main 쓰레드가 잠을 잠
-> try-catch 구문을 안쓰고 이렇게 따로 delay라는 static 메서드를 만들어줘도 가능!(항상 필수예외처리니까)
public static void main(String[] args) {
ThreadEx8_1 th1 = new ThreadEx8_1();
ThreadEx8_2 th2 = new ThreadEx8_2();
th1.start();
th2.start();
delay(2000);
System.out.println("<<main 종료>>");
} // main
static void delay(long millis) {
try {
Thread.sleep(millis);
} catch(InterruptedException e) {}
}
Interrupt( )와 Interrupted( ) - 쓰레드의 작업을 취소한다.
- 대기상태(WAITING)인 쓰레드를 실행대기 상태(RUNNABLE)로 만든다.
- 대기상태(작업의 중단) sleep( ), join( ) wait( )
*관련 메서드*
void interrupt( ) | 쓰레드의 interrupted 상태를 false에서 true로 변경(기본은 false으로 초기화 되어있음) |
Boolean isInterrupted( ) | 쓰레드의 interrupted 상태를 반환 |
static Boolean interrupted( ) | 현재 쓰레드의 interrupted상태를 알려주고, false로 초기화 |
Mythread th = new Mythread();
th.start();
th.interrupt(); // 쓰레드 th에 interrupt()를 호출한다.
class Mythread extends Thread {
public void run() {
while(!interrupted()) { // interrupted()의 결과가 false인 동안 반복
}
}
예제) 카운트다운이 되다가 사용자가 값을 입력하면 interrupt( )에 의해서 멈추고 카운트가 종료되는 프로그램
import javax.swing.JOptionPane;
public class ThreadEx13 {
public static void main(String[] args) {
ThreadEx13_1 th1 = new ThreadEx13_1();
th1.start();
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요");
System.out.println("입력하신 값은 " + input + "입니다.");
th1.interrupt(); // interrupt()를 호출하면, interrupted상태가 true가 된다.
System.out.println("isInterrupted():" + th1.isInterrupted()); // true
}
}
class ThreadEx13_1 extends Thread {
public void run() {
int i = 10;
while(i!=0 && !isInterrupted()) {
System.out.println(i--);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
interrupt(); //(1)
}
}
System.out.println("카운트가 종료되었습니다.");
}
}
suspend( ), resume( ), stop( )
- 쓰레드의 실행을 일시정지, 재개, 완전정지 시킨다.
void suspend( ) | 쓰레드를 일시정지 시킨다. |
void resume( ) | suspend( )에 의해 일시 정지된 쓰레드를 실행대기상태로 만든다. |
void stop( ) | 쓰레드를 즉시 종료시킨다. |
-> suspend( )와 stop( )이 교착상태(deadlock)를 일으키기 쉽게 작성되어 있어서 사용 권장 xx
-> 그래서 이 메서드들은 모두 'deprecated' 되었다.
-> 교착상태: 두 개 이상의 작업이 서로 상대방의 작업이 끝나기 만을 기다리고 있기 때문에 결과적으로 아무것도 완료되지 못하는 상태
-> deprecated: 전에는 사용되었지만, 앞으로 사용하지 않을 것을 권장한다. deprecated된 메서드는 하위 호환성을 위해서 삭제하지 않는 것
join( ) – 다른 쓰레드의 작업을 기다린다.
- 쓰레드 자신이 하던 작업을 잠시 멈추고 다른 쓰레드가 지정된 시간동안 작업을 수행하도록 할 때 join( )을 사용한다.
- join( )도 sleep( )처럼 interrupt( )에 의해 대기상태에서 벗어날 수 있고, join( )이 호출되는 부분을 try-catch문으로 감싸야 한다.
-> sleep( )과 유사하지만 sleep( )은 static 메서드이고, join( )은 현재 쓰레드가 아닌 특정 쓰레드에 대해 동작함
void join( ) | 작업이 모두 끝날 때까지 |
void join(long millis) | 천분의 일초 동안 |
void joint(long millis, int nanos) | 천분의 일초 + 나노초 동안 |
-> 시간을 지정하지 않으면, 해당 쓰레드가 작업을 모두 마칠 때까지 기다리게 된다. 작업 중에 다른 쓰레드의 작업이 먼저 수행되어야 할 필요가 있을 때 join을 사용
예제)
public class ThreadEx19 {
static long startTime = 0;
public static void main(String[] args) {
ThreadEx19_1 th1 = new ThreadEx19_1();
ThreadEx19_2 th2 = new ThreadEx19_2();
th1.start();
th2.start();
startTime = System.currentTimeMillis();
try {
th1.join(); // main쓰레드가 th1의 작업이 끝날때까지 기다린다.
th2.join(); // main쓰레드가 th2의 작업이 끝날때까지 기다린다.
} catch (InterruptedException e) { }
System.out.println("소요시간: " + (System.currentTimeMillis()-ThreadEx19.startTime));
}
}
class ThreadEx19_1 extends Thread {
public void run() {
for(int i=0; i<100; i++) {
System.out.print(new String("-"));
}
}//run( )
}
class ThreadEx19_2 extends Thread {
public void run() {
for(int i=0; i<100; i++) {
System.out.print(new String("|"));
}
}//run( )
}
<출력값>
-----------------------------------------------------|||-|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||----------------------------------------------소요시간: 41 -----------------------------------------------------|||-|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||----------------------------------------------소요시간: 41
-> join( )을 사용해서 main쓰레드가 쓰레드 th1과 th2의 작업을 마칠 때까지 기다리도록 했고, 소요시간: 이 제일 마지막에 출력됐다. 만약 주석으로 try-catch구문을 없애게 되면 main메서드가 종료되면서 소요시간은 0이된다.
yield( ) – 다른 쓰레드에게 양보한다.
- 쓰레드 자신에게 주어진 실행시간을 다음 차례의 쓰레드에게 양보하고, 자신(현재 쓰레드)은 실행대기한다. (static 메서드)
- yield( )와 interrupt( )를 적절히 사용하면, 응답성과 효율을 높일 수 있다.
- OS 스케줄러에게 통보하는 것이라서 정확하게 시간이 나눠지지는 않는다.
ex) 스케줄러에 의해 1초의 실행시간을 할당 받은 쓰레드가 0.5초의 시간동안 작업한 상태에서 yield( )가 호출되면, 나머지 0.5초는 포기하고 다시 실행대기상태가 된다.
https://everysmallstep.tistory.com/106
자바의 정석10-1 프로세스와 쓰레드
프로세스와 쓰레드(process & thread) - 프로세스: 실행 중인 프로그램, 자원(resources데이터와 메모리)과 쓰레드로 구성 - 쓰레드: 프로세스 내에서 실제 작업을 수행. 모든 프로세스는 최소한 하나의
everysmallstep.tistory.com
'멀티캠퍼스 풀스택 과정 > Java의 정석' 카테고리의 다른 글
자바의 정석11-1 람다와 함수형인터페이스 (0) | 2022.01.16 |
---|---|
자바의 정석10-5 쓰레드의 동기화(Synchronization) (0) | 2022.01.13 |
자바의 정석10-3 쓰레드의 우선순위와 쓰레드 그룹 (0) | 2022.01.13 |
자바의 정석10-2 싱글쓰레드와 멀티쓰레드, 쓰레드의 I/O 블락킹 (0) | 2022.01.12 |
자바의 정석10-1 프로세스와 쓰레드 (0) | 2022.01.12 |