이번에는 스레드 상태에 이어서 스레드 상태 제어에 대하여 알아 보겠습니다.
스레드 상태에 대한 부분은 다음의 링크를 통해 제 글을 보고 와주세요!
(제 설명이 부족했을 수도 있으니 검색을 통해서 본인이 알아보고 오셔도 좋습니다!)
스레드 상태 제어는 총 3개의 part로 나누어서 진행할 예정입니다.
스래드의 상태 중 실행, 실행 대기, 일시 정지 3가지를 조금 더 구체적으로 살펴보면 다음과 같습니다.
그림 1 에서 잘 보이지 않지만 취소선이 있는 suspend(), resume(), stop() 이 3가지는 스레드의 안전성을 해친다고 하여 더 이상 사용하지 않도록 권장된 Deprecated 메소드라고 합니다.
스레드 상태 제어와 관련된 메소드들을 다음의 표를 통하여 알아보도록 하겠습니다.
메소드 | 설명 |
interrupt() | 일시 정지 상태의 스레드에서 InterruptedException 예외를 발생시켜, 예외 처리 코드(catch)에서 실행 대기 상태로 가거나 종료 상태로 갈 수 있도록 한다. |
notify() notifyAll() |
동기화 블록 내에서 wait() 메소드에 의해 일시 정지 상태에 있는 스레드를 실행 대기 상태로 만든다. |
resume() |
suspend(0 메소드에 의해 일시 정지 상태에 있는 스레드를 실행 대기 상태로 만든다. - Deprecated (대신 notify(), notifyAll() 사용) |
sleep(long millis) sleep(long millis, int nanos) |
주어진 시간 동안 스레드를 일시 정지 상태로 만든다. 주어진 시간이 지나면 자동적으로 실행 대기 상태가 된다. |
join() join(long millis) join(long millis, int nanos) |
join() 메소드를 호출한 스레드는 일시 정지 상태가 된다. 실행 대기 상태로 가려면, join() 메소드를 멤버로 가지는 스레드가 종료되거나, 매개값으로 주어진 시간이 지나야 한다. |
wait() wait(long millis) join(long millis, int nanos) |
동기화(synchronized) 블록 내에서 스레드를 일시 정지 상태로 만든다. 매개값으로 주어진 시간이 지나면 자동적으로 실행 대기 상태가 된다. 시간이 주어지지 않으면 notify(), notifyAll() 메소드에 의해 실행 대기 상태로 갈 수 있다. |
suspend() | 스레드를 일시 정지 상태로 만든다. resume() 메소드를 호출하면 다시 실행 대기 상태가 된다. - Deprecated (대신 wait() 사용) |
yield() | 실행 중에 우선순위가 동일한 다른 스레드에게 실행을 양보하고 실행 대기 상태가 된다. |
stop() | 슬드를 즉시 종료시킨다. - Deprecated |
위 표에서 wait(), notify(), notifyAll() 은 Object 클래스의 메소드이고, 그 이외의 메소드는 Thread 클래스의 메소드입니다.
이번 글에서는 sleep(), yield(), join() 메소드에 대하여 알아 보도록 하겠습니다.
1. 주어진 시간동안 일시 정지(sleep())
· Thread.sleep() 메소드를 호출한 스레드는 주어진 시간 동안 일시 정지 상태가 되고, 다시 실행 대기 상태로 돌아갑니다.
· 매개값에는 얼마 동안 일시 정지 상태로 있을 것인지, 밀리세컨드(1/1000) 단위로 시간을 주면 됩니다. ex) Thread.sleep(3000); ->3초
· 일시 정지 상태에서 주어진 시간이 되기 전에 interrupt() 메소드가 호출되면 InterruptedException이 발생하기 때문에
예외 처리가 필요합니다.
다음은 sleep() 예제 입니다.
SleepExample.java - 3초 주기로 10번 비프음 발생
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package thread_state_control;
import java.awt.Toolkit;
public class SleepExample {
public static void main(String[] args) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
for(int i=0; i<10; i++) {
try {
} catch(InterruptedException e) {
}
}
}
}
|
실행해보면 정상적으로 소리가 3초 간격으로 10번 동안 나는 것을 확인 하실 수 있습니다.
2. 다른 스레드에게 실행 양보(yield())
· yield()는 실행 되고 있는 스레드 주체가 혼자만 작동하지 않고, 다른 스레드가 실행 기회를 가질 수 있도록 해줍니다.
· yield()는 sleep()과 함께 글의 마지막 부분에 제 나름대로의 설명을 추가하도록 하겠습니다.
다음은 yield()를 사용 했을 때의 스레드들의 상태 변화를 나타낸 것입니다.
다음은 yield() 예제 입니다.
YieldExample.java - 스레드 실행 양보 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package thread_state_control;
public class YieldExample {
public static void main(String[] args) {
ThreadA threadA = new ThreadA();
ThreadB threadB = new ThreadB();
// ThreadA, ThreadB 모두 실행
// ThreadA, ThreadB 모두 종료
}
}
|
ThreadA.java - 스레드 실행 양보 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package thread_state_control;
public class ThreadA extends Thread {
public boolean stop = false;
public boolean work = true;
@Override
public void run() {
while(!stop) { // stop이 true가 되면 while문 종료
if(work) {
System.out.println("ThreadA 작업 내용");
} else {
Thread.yield(); // work가 false가 되면 다른 스레드에게 실행 양보
}
}
System.out.println("ThreadA 종료");
}
}
|
ThreadB.java - 스레드 실행 양보 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package thread_state_control;
public class ThreadB extends Thread {
public boolean stop = false;
public boolean work = true;
@Override
public void run() {
while(!stop) { // stop이 true가 되면 while문 종료
if(work) {
System.out.println("ThreadB 작업 내용");
} else {
Thread.yield(); // work가 false가 되면 다른 스레드에게 실행 양보
}
}
System.out.println("ThreadB 종료");
}
}
|
실행을 하게 되면 ThreadA와 ThreadB가 번갈아가며 실행이 되다가 yield()로 인하여 ThreadB가 더 많은 실행 기회를 가지게 되어서
ThreadB만 실행이 되는 상황이 나옵니다.
그러다 다시 ThreadA와 ThreadB가 번갈아가며 실행이 되면서 결국 마지막에 모든 스레드가 종료가 됩니다.
3. 다른 스레드의 종료를 기다림(join())
· 스레드는 다른 스레드와 독립적으로 실행하는 것이 기본입니다.
· 하지만 다른 스레드가 종료될 때까지 기다렸다가 실행해야 하는 경우가 발생할 수도 있습니다.
· join() 메소드는 sleep(), yield() 와 함께 마지막 부분에서 제 나름대로의 방식으로 설명을 추가하겠습니다.
다음 그림은 join()을 사용 했을 때의 스레드 상태를 나타낸 것입니다.
다음은 yield()를 사용한 예제 입니다.
SumThread.java - 1부터 100까지 합을 계산하는 스레드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package thread_state_control;
public class SumThread extends Thread {
private long sum;
public long getSum() {
return sum;
}
public void setSum(long sum) {
this.sum = sum;
}
@Override
public void run() {
for(int i=1; i<=100; i++) {
sum+=i;
}
}
}
|
JoinExample.java - 다른 스레드가 종료될 때까지 일시 정지 상태 유지
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package thread_state_control;
public class JoinExample {
public static void main(String[] args) {
SumThread sumThread = new SumThread();
try {
sumThread.join(); // sumThread가 종료할 때까지 메인 스레드를 일시 정지시킴
} catch(InterruptedException e) {
}
}
}
|
join() 예제를 실행해 보시면 정상적으로 5050이 출력 되는 것을 확인 하실 수 있습니다.
하지만 JoinExample의 9 ~ 12 행인 try ~ catch 구문을 주석처리 하고 실행하면 0이 나오게 됩니다.
(교재에서는 컴퓨터 성능에 따라서 다른 값이 출력 될 수도 있다고 합니다. 저는 0이 나왔습니다.)
이러한 이유는 SumThread가 계산 작업을 완료하지 않은 상태에서 합을 먼저 출력하기 때문입니다.
이렇듯 sleep(), yield(), join()에 대하여 알아 보았습니다.
개인적으로 sleep() yield() join()이 다 비슷비슷 하게 느껴져서 헷갈리지만 제 나름대로의 생각은 다음과 같습니다.
1. sleep()의 예시
- 혼자서 프린터를 사용하여 문서 1000장을 복사하는데 너무 지치니까 100장 마다 5분 씩 쉬고 다시 복사를 합니다.
2. yield()의 예시
- 프린터를 사용하여 문서 1000장을 복사하는데 복사하는 도중, 다른 사람이 사용하려 하면 사용하도록 하고 자신은 잠시 쉽니다.
3. join()의 예시
- 하나의 내용에 대한 문서가 2000장 입니다. 이 때, 1000장 씩 나눠서 역할을 분담 받았습니다.
단, 2000장의 문서가 모두 모여야 내용이 완전해지고, 그렇지 않을 경우 의미 없고, 알 수 없는 내용의 문서가 되어 버립니다.
- 결국 다른 사람이 담당하는 문서의 내용을 합하여 하나의 내용으로 만들어야 하기 때문에
다른 사람이 담당하는 역할이 끝날 때까지 기다립니다.
대략 이런 식으로 sleep(), yield(), join()을 이해를 해 봤습니다. (정확한지는 모르겠습니다.)
피드백을 주시면 감사히 받겠습니다.
'프로그래밍 언어 > Java' 카테고리의 다른 글
Java - 데몬 스레드 (Daemon Thread) (0) | 2019.08.16 |
---|---|
Java - 스레드 상태 제어 (Thread State Control) part 3 (2) | 2019.08.16 |
Java - 스레드 상태 제어 (Thread State Control) part 2 (0) | 2019.08.16 |
Java - 스레드 상태(Thread State) (0) | 2019.08.11 |
Java 멀티 스레드 - Synchronized(동기화) (0) | 2019.08.10 |