자바의 정석 13장: 쓰레드(thread)
[ 출처 ]
자바의 정석:
https://product.kyobobook.co.kr/detail/S000001550352
자바의 정석 유튜브:
https://www.youtube.com/playlist?list=PLW2UjW795-f6xWA2_MUhEVgPauhGl3xIp



13-1 프로세스와 쓰레드(process & thread)
- 프로세스 : 실행 중인 프로그램. 프로그램을 실행하면 OS로부터 실행에 필요한 자원(resources : 메모리, CPU)을 할당받아 프로세스가 된다.
- 쓰레드 : 프로세스 내에서 실제 작업을 수행. 모든 프로세스는 최소 하나의 쓰레드를 가지고 있다.💡프로세스 : 쓰레드 = 공장 : 일꾼
멀티 쓰레드로 프로그램을 작성하게 되면 한 프로세스 내에 일꾼이 여러 명이기 때문에 여러 작업을 나누어서 동시에 수행할 수 있고 작업을 더 효율적으로 수행할 수 있게된다.

→ 우리가 사용하는 대부분의 프로그램은 멀티 쓰레드로 작성되어있다.
- 2 프로세스 1 쓰레드 VS 1 프로세스 2 쓰레드
공장을 새로 짓고 일꾼을 쓰는 것보다 / 공장을 새로 짓지 않고 일꾼만 더 고용해서 작업을 하는 것이 비용이 더 적다는 게 당연한 것처럼
프로세스를 새로 생성하는 것보다 하나의 새로운 쓰레드를 생성하는 것이 더 적은 비용이 든다.
13-2 멀티쓰레드의 장단점
장점
- 시스템 자원을 보다 효율적으로 사용할 수 있다.
- 사용자에 대한 응답성(responseness)이 향상된다.
메신저로 채팅하면서 파일을 다운로드 받거나 음성대화를 나눌 수 있는 것이 가능한 이유가 바로 멀티쓰레드로 작성되어 있기 때문이다. 만일 싱글쓰레드로 작성되어 있다면 파일을 다운로드 받는 동안에는 다른 일(채팅)을 전혀 할 수 없을 것이다.(한 번에 한가지 작업밖에 못 하기 때문)
- 작업이 분리되어 코드가 간결해진다.
단점
- 동기화(synchronization)에 주의해야 한다.
같은 공장 내에서 여러가지 자원이 있다. 망치.. 톱..
이것을 여러 쓰레드가 공유함으로써 생기는 문제점이 동기화다.
- 교착상태(dead-lock)가 발생하지 않도록 주의해야 한다.
A는 망치를 들고 있고 B는 톱을 들고 있는데 A는 톱이 필요한 상태, B는 망치가 필요한 상태.
그런데 서로에게 필요한 자원을 가지고서 B는 니가 톱을 주면 망치를 준다고하고 A는 니가 망치를 주면 톱을 준다고 함.
이렇게 둘이서 대치하고 있는 상태이다. → 작업 진행 안됨. 이런 상태를 교착상태(dead-lock)라고 한다.
- 각 쓰레드가 효율적으로 고르게 실행될 수 있게 해야 한다.
→ 프로그래맹할 때 고려해야 할 사항들이 많다.
13-3 쓰레드의 구현과 실행
쓰레드를 구현하는 방법은 Thread 클래스를 상속받는 방법과 Runnable 인터페이스를 구현하는 방법 두 가지가 있다. 어느 쪽을 선택해도 별 차이는 없지만 Thread 클래스를 상속받으면 다른 클래스를 상속받을 수 없기 때문에, Runnable 인터페이스를 구현하는 방법이 일반적이다.
Runnable 인터페이스를 구현하는 방법은 재사용성(reusability)이 높고 코드의 일관성(consistency)을 유지할 수 있기 때문에 보다 객체지향적인 방법이다.
// 1. Thread 클래스를 상속해서 쓰레드를 구현
class MyThread extends Thread {
public void run() { /* 작업내용 */ } // Thread 클래스의 run()을 오버라이딩
}
// 2. Runnable 인터페이스를 구현해서 쓰레드를 구현
class MyThread implements Runnable {
public void run() { /* 작업내용 */ } // Runnable 인터페이스의 추상메서드 run()을 오버라이딩
}
우리가 main() { } 안에 작업 내용을 넣는 것 처럼 run()에도 똑같이 쓰레드가 수행할 작업 내용을 넣어주면 된다.
예제 13-1
static Thread currentThread() 현재 실행중인 쓰레드의 참조를 반환한다. String getName() 쓰레드의 이름을 반환한다.
package chapter13; public class Ex13_1 { public static void main(String[] args) { ThreadEx1 t1 = new ThreadEx1(); Runnable r = new ThreadEx2(); // 조상이어서 ThreadEx2 인스턴스를 참조할 수 있다. Thread t2 = new Thread(r); // 생성자 Runnable target t1.start(); t2.start(); } } class ThreadEx1 extends Thread { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(getName()); } } } class ThreadEx2 implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { // Thread.currentThread() - 현재 실행중인 Thread를 반환한다. System.out.println(Thread.currentThread().getName()); } } }
13-5 쓰레드의 실행 - start()
- 쓰레드를 생성했다고 해서 자동으로 실행되지 않는다. start()를 호출해야 쓰레드가 작업을 시작한다.
ThreadEx1_1 t1 = new ThreadEx1_1(); ThreadEx1_1 t2 = new ThreadEx1_1(); t1.start(); // 쓰레드 t1을 실행시킨다. t2.start(); // 쓰레드 t2을 실행시킨다.
사실은 start()가 호출되었다고 바로 실행되는 것이 아니라, 일단 실행대기 상태에 있다가 자신의 차례가 되어야 실행된다. 물론 실행 대기 중인 쓰레드가 하나도 없으면 곧바로 실행상태가 된다.
쓰레드의 실행순서는 OS의 스케쥴러가 작성한 스케줄에 의해 결정된다. t1을 먼저 start()했다고 쓰레드 t1이 무조건 먼저 실행되지 않는다.
- 종료된 쓰레드는 다시 실행할 수 없다.
하나의 쓰레드에 대해 start()가 한 번만 호출될 수 있음.
그래서 만일 쓰레드의 작업을 한 번 더 수행해야 한다면 아래의 오른쪽 코드와 같이 새로운 쓰레를 생성한 다음에 start()를 호출해야 한다. 만일 아래 왼쪽의 코드처럼 하나의 쓰레드에 대해 start()를 두 번 이상 호출되면 실행 시에 IllegalThreadStateException이 발생한다.
ThreadEx1_1 t1 = new ThreadEx1_1(); t1.start(); t1.start(); // 예외발생
→
ThreadEx1_1 t1 = new ThreadEx1_1(); t1.start(); t1 = new ThreadEx1_1(); // 다시 생성 t1.start(); // OK
13-6 start()와 run()
쓰레드를 실행시킬 때 run()이 아닌 start()를 호출한다는 것에 의문이 들었을 것이다.
start()와 run()의 차이와 쓰레드가 실행하는 과정에 대해서 자세히 살펴보자.
main메서드에서 run()을 호출하는 것은 생성된 쓰레드를 실행시키는 것이 아니라 단순히 클래스에 선언된 메서드를 호출하는 것일 뿐이다. (멀티 쓰레드가 아닌 하나의 쓰레드에서 run이 실행되는 것)
쓰레드의 실행 과정
반면에 start()는 새로운 쓰레드가 작업을 실행하는데 필요한 호출스택(call stack)을 생성한 다음에 run()을 호출해서, 생성된 호출스택에 run()이 첫 번째로 올라가게 한다.
모든 쓰레드는 독립적인 작업을 수행하기 위해 자신만의 호출스택을 필요로 하기 때문에, 새로운 쓰레드를 생성하고 실행시킬 때마다 새로운 호출스택이 생성되고 쓰레드가 종료되면 작업에 사용된 호출스택은 소멸된다.
① main 메서드에서 쓰레드의 start()를 호출한다.
② start()는 새로운 쓰레드를 생성하고, 쓰레드가 작업하는데 사용될 호출스택을 생성한다.
③ 새로 생성된 호출스택에 run()이 호출되어, 쓰레드가 독립된 공간에서 작업을 수행한다.
④ 이제는 호출스택이 2개이므로 스케줄러가 정한 순서에 의해서 번갈아 가면서 실행된다.
13-7 main 쓰레드
- main 메서드의 코드를 수행하는 쓰레드
public class Hello { public static void main(String[] args) { System.out.println("Hello, world."); } }
java 인터프리터가 Hello 클래스에 있는 main 메서드를 호출한다.
- 쓰레드에는 ‘사용자 쓰레드’와 ‘데몬 쓰레드’ 두 종류가 있다.
main 쓰레드 - 사용자 쓰레드
데몬 쓰레드 - 사용자 쓰레드가 하는 작업을 보조하는 역할
메인 메서드가 종료되어도 다른 프로그램이 실행 중이면 프로그램이 종료되지 않는다.
13-8 싱글쓰레드와 멀티쓰레드
아래에서 알 수 있듯이 하나의 쓰레드로 두 개의 작업을 수행한 시간과 두 개의 쓰레드로 두 개의 작업을 수행한 시간은 거의 같다. 오히려 두 개의 쓰레드로 작업한 시간이 싱글쓰레드로 작업한 시간보다 더 걸리게 되는데 그 이유는 쓰레드간의 작업 전환(context switching)에 시간이 걸리기 때문이다.
작업 전환을 할 때는 현재 진행 중인 작업의 상태, 예를 들면 다음에 실행해야 할 위치(PC, 프로그램 카운터) 등의 정보를 저장하고 읽어오는 시간이 소요된다.
+) 쓰레드의 스위칭에 비해 프로세스의 스위칭이 더 많은 정보를 저장해야하므로 더 많은 시간이 소요된다.
그래서 싱글 코어에서 단순히 CPU만을 사용하는 계산작업이라면 오히려 멀티쓰레드보다 싱글쓰레드로 프로그래밍하는 것이 효율적이다.
싱글쓰레드

class ThreadTest {
public static void main(String[] args) {
for (int i = 0; i < 300; i++) {
System.out.print("-");
}
for (int i = 0; i < 300; i++) {
System.out.print("|");
}
} // main
}
멀티쓰레드

class ThreadTest {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t1.start();
t2.start();
} // main
}
class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 300; i++) {
System.out.print("-");
}
} // run()
}
class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 300; i++) {
System.out.print("|");
}
}
}
13-11 쓰레드의 I/O블락킹(blocking)
만일 사용자로부터 입력받는 작업(A)과 화면에 출력하는 작업(B)을 하나의 쓰레드로 처리한다먼 왼쪽의 그래프처럼 사용자가 입력을 마칠 때까지 아무 일도 하지 못하고 기다리기만 해야 한다.
그러나 두 개의 쓰레드로 처리한다면 사용자의 입력을 기다리는 동안 다른 쓰레드가 작업을 처리할 수 있기 때문에 보다 효율적인 CPU의 사용이 가능하다.
작업 A와 B가 모두 종료되는 시간 t2와 t2’를 비교하면 t2 > t2’로 멀티 쓰레드 프로세스의 경우가 작업을 더 빨리 마치는 것을 알 수 있다.
싱글 쓰레드 프로세스

String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
System.out.println("입력하신 값은" + input + "입니다.");
for (int i = 10; i > 0; i--) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}

멀티 쓰레드 프로세스

class Main {
public static void main(String args[]) {
ThreadEx5 th1 = new ThreadEx5();
th1.start();
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
System.out.println("입력하신 값은" + input + "입니다.");
} // main
}
class ThreadEx5 extends Thread {
@Override
public void run() {
for (int i = 10; i > 0; i--) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
} // run()
}
13-14 쓰레드의 우선순위(priority nof thread)
- 작업의 중요도에 따라 쓰레드의 우선순위를 다르게 하여 특정 쓰레드가 더 많은 작업시간을 갖게 할 수 있다.
void setPriority(int newPriority) 쓰레드의 우선순위를 지정한 값으로 변경한다.
int getPriority() 쓰레드의 우선순위를 반환한다.
public static final int MAX_PRIORITY = 10 // 최대 우선선위
public static final int MIN_MAX_PRIORITY = 1; // 최소 우선순위
public static final int NORM_MAX_PRIORITY = 5; // 보통 우선순위

but, 우리가 정해주는 쓰레드의 우선순위는 희망사항에 불과함 무조건 내가 지정한 우선순위대로 실행되지 않는다.
OS에서 돌아가는 수많은 프로세스와 쓰레드들이 있는데 그것을 무시하고 우리가 실행하는 프로그램에게만 특혜를 줄 수 없기 때문이다.
OS의 스케줄러는 해당 OS에서 돌아가는 모든 프로세스와 쓰레드에 공평하게 돌아가야 한다.
예제 13-6
package chapter13; public class Ex13_6 { public static void main(String[] args) { ThreadEx6_1 th1 = new ThreadEx6_1(); ThreadEx6_2 th2 = new ThreadEx6_2(); th2.setPriority(7); // 쓰레드 우선순위 설정s System.out.println("Priorty of th1(-) : " + th1.getPriority()); // 쓰레드의 우선순위 출력 System.out.println("Priorty of th2(|) : " + th2.getPriority()); th1.start(); th2.start(); } } class ThreadEx6_1 extends Thread { @Override public void run() { for (int i = 0; i < 300; i++) { System.out.print("-"); for (int x = 0; x < 1000000; x++); // 작업을 지연시키기 위한 for문 } } } class ThreadEx6_2 extends Thread { @Override public void run() { for (int i = 0; i < 300; i++) { System.out.print("|"); for (int x = 0; x < 1000000; x++); // 작업을 지연시키기 위한 for문 } } }
+) 윈도우의 마우스 포인터는 우선순위가 높다고한다. 다른 작업이 안 돌아가는데 마우스 포인터까지 멈추면 사용자가 답답해할 수 있기 때문이다.
13-16 쓰레드 그룹
- 서로 관련된 쓰레드를 그룹으로 묶어서 다루기 위한 것
- 모든 쓰레드는 반드시 하나의 쓰레드 그룹에 포함되어 있어야 한다.
- 쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 ‘main 쓰레드 그룹’에 속한다.
Thread(ThreadGroup group, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
ThreadGroup getThreadGroup() 쓰레드 자신이 속한 쓰레드 그룹을 반환한다.
void uncaughtException(Thread t, Throwable e) 처리되지 않은 예외에 의해 쓰레드 그룹의 쓰레드가 실행이 종료되었을 때, JVM에 의해 이 메서드가 자동적으로 호출된다.
13-17 쓰레드 그룹의 메서드
쓰레드는 그룹으로 묶여서 다뤄진다. → 이거만 알면된다. 깊게 볼 필요 X
13-18 데몬 쓰레드(daemon thread)
- 일반 쓰레드(non-daemon thread)의 작업을 돕는 보조적인 역할을 수행.
- 일반 쓰레드가 모두 종료되면 자동적으로 종료된다.
- 가비지 컬렉터(GC), 자동저장, 화면 자동갱신 등에 사용된다.
- 무한루프와 조건문을 이용해서 실행 후 대기하다가 특정조건이 만족되면 작업을 수행하고 다시 대기하도록 작성한다.
public void run() { while (true) { // 일반 쓰레드가 종료되면 자동적으로 종료되기 때문에 무한 루프를 사용해도 괜찮다. try { Thread.sleep(3 * 1000); // 3초마다 } catch (InterruptedException e) { } if (autoSave) { // autoSave의 값이 true이면 autoSave()를 호출한다. autoSave(); } } }
boolean isDaemon() // 쓰레드가 데몬 쓰레드인지 확인한다. 데몬 쓰레드이면 true를 반환
void setDaemon(boolean on) // 쓰레드를 데몬 쓰레드로 또는 사용자 쓰레드로 변경 매개변수 on을 true로 지정하면 데몬 쓰레드가 된다.
예제 13-7
package chapter13; 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(); t.interrupt(); 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("작업파일이 자동저장되었습니다."); } }
13-19 쓰레드의 상태
상태 | 설명 |
---|---|
NEW | 쓰레드가 생성되고 아직 start()가 호출되지 않은 상태 |
RUNNABLE | 실행 중 또는 실행 가능한 상태 |
BLOCKED | 동기화블럭에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태) |
WAITING, TIMED_WAITING | 쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrunnable) 일시정지상태. TIMED_WAITING은 일시정지시간이 지정된 경우를 의미 |
TERMINATED | 쓰레드의 작업이 종료된 상태 |
13-21 쓰레드의 실행제어
- 쓰레드의 실행을 제어할 수 있는 메서드가 제공된다.
이들을 활용해서 보다 효율적인 프로글매을 작성할 수 있다.
메서드 | 설명 |
---|---|
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이 붙은 sleep과 yield는 자기 자신에게만 호출가능하다. (다른 쓰레드에게 적용 불가능)
sleep() → 내가 잘 수는 있어도 다른 사람을 재울 수는 없다.
yeild() → 도 마찬가지로 내가 양보를 할 수는 있어도 다른 사람에게 양보하라고 할 수 없음
13-22 sleep()
- 현재 쓰레드를 지정된 시간동안 멈추게 한다.
static void sleep(long millis) // 천분의 일초, 3초 = 3 * 1000 static void sleep(long millis, int nanos) // 천분의 일초 + 나노초
- Exception의 자손이라 checked 예외. 예외처리를 해야 한다.(InterruptedException이 발생하면 깨어남)
try { Thread.sleep(1, 500000); // 쓰레드를 0.0015초동안 멈추게 한다. } catch (InterruptedException e) {} // Exception의 자손. 필수 예외처리
항상 예외처리를 해야 하기 때문에 불편해서 메서드를 따로 만들어 사용하기도 한다.
void delay(long millis) { try { Thread.sleep(millis);v } catch (InterruptedException e) {} }
- 특정 쓰레드를 지정해서 멈추게 하는 것은 불가능하다.
try { // No error, th1을 sleep한다고 오해할 수 있음 th1.sleep(2000); } catch (InterruptedException e) {}
→
try { // 클래스 이름을 써서 호출해야 오해의 여지가 없음 Thread.sleep(2000); } catch (InterruptedException e) {}
Thread 실행제어 static 메서드 - 자기 자신에게만 동작한다.
- sleep()
- yield() - 양보
예제 13-8
package Test; class Main { 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 종료>>"); } public static void delay(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) {} } } class ThreadEx8_1 extends Thread { @Override public void run() { for (int i = 0; i < 300; i++) { System.err.print("-"); } System.err.println("<<th1 종료>>"); } // run() } class ThreadEx8_2 extends Thread { @Override public void run() { for (int i = 0; i < 300; i++) { System.err.print("|"); } System.err.println("<<th2 종료>>"); } // run() }
13-24 interrupt()
- 대기상태(WAITING)인 쓰레드를 실행대기 상태(RUNNABLE)로 만든다.
void interrupt() // 쓰레드의 interrupted 상태를 false에서 true로 변경
boolean isInterrupted() // 쓰레드의 interrupted 상태를 반환
static boolean interrupted() // 현재 쓰레드의 interrupted 상태를 알려주고, false로 초기화
진행 중인 쓰레드의 작업이 끝나기 전에 취소시켜야 할 때가 있다. 예를 들어 큰 파일을 다운로드받을 때 시간이 너무 오래 걸리면 중간에 다운로드를 포기하고 취소할 수 있어야 한다. interrupt()는 쓰레드에게 작업을 멈추라고 요청한다. 단지 멈추라고 요청하는 것일 뿐 쓰레드를 강제로 종료시키지는 못한다. interrupte()는 그저 쓰레드의 interrupted상태(인스턴스 변수)를 바꾸는 것일 뿐이다.
\
Thread th = new Thread();
th.start();
...
th.interrupt(); // 쓰레드 th에 interrupt()를 호출한다.
...
class MyThread extends Thread {
public void run() {
while (!interrupted()) { // interrupted()의 결과가 false인 동안 반복
}
}
}
interrupted()가 호출되면, interrupted()의 결과가 false에서 true로 바뀌어 while문을 벗어나게 된다. while문의 조건식에 ‘!’가 포함되어 있는 것에 주의하자.
interrupted가 true → 누군가 나를 방해했음
예제 13-9
package Test; import javax.swing.JOptionPane; class Main { public static void main(String[] args) { ThreadEx9_1 th1 = new ThreadEx9_1(); th1.start(); String input = JOptionPane.showInputDialog("아무 값이나 입력하세요."); System.out.println("입력하신 값은" + input + "입니다."); th1.interrupt(); // interrupt()를 호출하면, interrupted 상태가 ture가 된다. System.out.println("isInterrupted() : " + th1.isInterrupted()); } } class ThreadEx9_1 extends Thread { @Override public void run() { int i = 10; while (i != 0 && !isInterrupted()) { System.out.println(i--); for (long x = 0; x < 2500000000L; x++); // 시간 지연 } System.out.println("카운트가 종료되었습니다."); } }
13-26 suspend(), resume(), stop()
- 쓰레드의 실행을 일시정지, 재개, 완전정지 시킨다.
void suspend() // 쓰레드를 일시정지 시킨다. void resume() // suspend()에 의해 일시정지된 쓰레드를 실행대기상태로 만든다. vodi stop() // 쓰레드를 즉시 종료시킨다.
- suspend(), resume(), stop()은 교착상태에 빠지기 쉬워서 deprecated 되었다.
class ThreadEx17_1 implements Runnable { boolean suspended = false; // 일시정지 boolean stopped = false; // 정지 @Override public void run() { while (!stopped) { if (!suspended) { /* 쓰레드가 수행할 코드르 작성 */ } } } public void suspend() { suspended = true; } public void resume() { suspended = false; } public void stop() { stopped = true; } }
예제 13-10 - deprecated 메서드 사용
package chapter13; class Ex13_10 { public static void main(String args[]) { RunImplEx10 r = new RunImplEx10(); Thread th1 = new Thread(r, "*"); Thread th2 = new Thread(r, "**"); Thread th3 = new Thread(r, "***"); th1.start(); th2.start(); th3.start(); try { Thread.sleep(2000); th1.suspend(); // 쓰레드 th1을 잠시 중단시킨다. Thread.sleep(2000); th2.suspend(); Thread.sleep(3000); th1.resume(); // 쓰레드 th1이 다시 동작하도록 한다. Thread.sleep(3000); th1.stop(); // 쓰레드 th1을 강제종료시킨다. th2.stop(); Thread.sleep(2000); th3.stop(); } catch (InterruptedException e) {} } // main } class RunImplEx10 implements Runnable { public void run() { while(true) { System.out.println(Thread.currentThread().getName()); try { Thread.sleep(1000); } catch(InterruptedException e) {} } } // run() }
예제 13-10
package Test; class Main { public static void main(String args[]) { MyThread th1 = new MyThread("*"); MyThread th2 = new MyThread("**"); MyThread th3 = new MyThread("***"); th1.start(); th2.start(); th3.start(); try { Thread.sleep(2000); th1.suspend(); // 쓰레드 th1을 잠시 중단시킨다. Thread.sleep(2000); th2.suspend(); Thread.sleep(3000); th1.resume(); // 쓰레드 th1이 다시 동작하도록 한다. Thread.sleep(3000); th1.stop(); // 쓰레드 th1을 강제종료시킨다. th2.stop(); Thread.sleep(2000); th3.stop(); } catch (InterruptedException e) {} } // main } class MyThread implements Runnable { volatile boolean suspended = false; volatile boolean stopped = false; Thread th; MyThread(String name) { th = new Thread(this, name); } void start() { th.start(); } void stop() { stopped = true; } void suspend() { suspended = true; } void resume() { suspended = false; } @Override public void run() { while (!stopped) { if (!suspended) { System.out.println(Thread.currentThread().getName()); try { Thread.sleep(1000); } catch(InterruptedException e) {} } } } // run() }
13-28 join()
- 지정된 시간동안 특정 쓰레드가 작업하는 것을 기다린다.
void join() // 작업이 모두 끝날 때까지 void join(long millis) // 천분의 일초 동안 void join(long millis, int nanos) // 천분의 일초 + 나노초 동안
- 예외처리를 해야 한다.(InterruptedException이 발생하면 작업 재개)
public static void main(String args[]) { ThreadEx11_1 th1 = new ThreadEx11_1(); ThreadEx11_2 th2 = new ThreadEx11_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() - startTime)); }
+) join()은 자신의 작업 중간에 다른 쓰레드의 작업을 참여(join)시킨다는 의미로 이름 지어진 것이다.
예제 13-11
join()을 사용하지 않았으면 main 쓰레드는 바로 종료되었겠지만, join()으로 쓰레드 th1과 th2의 작업을 마칠 때까지 main 쓰레드가 기다리도록 했다. 그래서 main 쓰레드가 두 쓰레드의 작업에 소요된 시간을 출력할 수 있다.
package chapter13; class Ex13_11 { static long startTime = 0; public static void main(String args[]) { ThreadEx11_1 th1 = new ThreadEx11_1(); ThreadEx11_2 th2 = new ThreadEx11_2(); th1.start(); th2.start(); startTime = System.currentTimeMillis(); // 시작시간 try { th1.join(); // main쓰레드가 th1의 작업이 끝날 때까지 기다린다. th2.join(); // main쓰레드가 th2의 작업이 끝날 때까지 기다린다. } catch(InterruptedException e) {} System.out.print("소요시간:" + (System.currentTimeMillis() - Ex13_11.startTime)); } // main } class ThreadEx11_1 extends Thread { public void run() { for(int i=0; i < 300; i++) { System.out.print(new String("-")); } } // run() } class ThreadEx11_2 extends Thread { public void run() { for(int i=0; i < 300; i++) { System.out.print(new String("|")); } } // run() }
join() 예시
@Override public void run() { while (true) { try { Thread.sleep(10 * 1000); // 10초를 기다린다. } catch (InterruptedException e) { System.out.println("Awaken by interrupt()."); } gc(); // garbage collection을 수행한다. (사용하지 않는 객체 제거) System.out.println("Garbage Collected. Free Memory : " + freeMemory()); } }
for (int i = 0; i < 20; i++) { requiredMemory = (int) (Math.random() * 10) * 20; // 필요한 메모리가 사용할 수 있는 양보다 적거나 전체 메모리의 60% 이상 사용했을 경우 gc를 깨운다. if (gc.freeMemory() < requiredMemory || gc.freeMemory() < gc.totalMemory() * 0.4) { gc.interrupt(); // 잠자고 있는 쓰레드 gc를 깨운다. try { gc.join(100); // gc가 메모리를 정리할 시간을 주어야 한다. } catch (InterruptedException e) {} } gc.usedMemory += requiredMemory; System.out.println("usedMemory : " + gc.usedMemory); }
13-28 yield()
- yield()와 interrupt()를 적절히 사용하면, 응답성과 효율을 높일 수 있다.
class MyThreadEx18 implements Runnable { boolean suspended = false; // 일시정지 boolean stopped = false; // 정지 Thread th; MyThreadEx18(String name) { th = new Thread(this, name); } // 이전 코드 @Override public void run() { while (!stopped) { // suspended가 true일 때(일시정지 상태) // -> busy-waiting : 나한테 시간이 주어졌지만 일시정지 상태라서 내가 할 수 있는 작업이 없다.(if 아래 구문은 실행이 안 되고 while만 의미없이 계속 반복하게 됨) if (!suspended) { try { Thread.sleep(1000); } catch (InterruptedException e) {} } // if } // while } }
void start() { th.start(); } void resume() { suspended = false; } void suspend() { suspended = true; } void stop() { stopped = true; }
class MyThreadEx18 implements Runnable { boolean suspended = false; // 일시정지 boolean stopped = false; // 정지 Thread th; MyThreadEx18(String name) { th = new Thread(this, name); } // 이전 코드 @Override public void run() { while (!stopped) { if (!suspended) { try { Thread.sleep(1000); } catch (InterruptedException e) {} } else { // 일시정지 상태일경우 자기 자신한테 주어진 시간을 다른 쓰레드에게 양보한다. Thread.yield(); } } // while } }
void start() { th.start(); } void resume() { suspended = false; } void suspend() { suspended = true; th.interrupt(); } void stop() { stopped = true; th.interrupt(); }
suspend()를 호출하고 내가 자고있을 수 있기 때문에 쓰레드를 깨워줘야 한다.
stop()이나 suspend()를 호출했는데 자고 있으면 1초 후에 멈추거나 1초 후에 일시정지가 되기 때문에 쓰레드를 깨워줘야 한다. → 응답성이 좋아진다는 것이 이런 뜻?
stop() 눌렀는데 바로 안 멈추면 응답성 구린거
yield()는 os스케줄러에게 그냥 알려주는거임 os 스케줄러 통보 반드시 동작하지 않음 스케줄러 맘임
13-30 쓰레드의 동기화(synchronization)
- 멀티 쓰레드 프로세스에서는 다른 쓰레드의 작업에 영향을 미칠 수 있다.
- 진행중인 작업이 다른 쓰레드에게 간섭받지 않게 하려면 ‘동기화’가 필요
- 동기화하려면 간섭받지 않아야 하는 문장들을 ‘임계 영역’으로 설정
- 임계영역은 락(lock)을 얻은 단 하나의 쓰레드만 출입가능(객체 1개에 락 1개)
13-31 synchronized를 이용한 동기화
- synchronized로 임계영역(lock이 걸리는 영역)을 설정하는 방법 2가지
// 1. 메서드 전체를 임계 영역으로 지정 public synchronized void calcSum() { // 임계 영역(critical section) //... } // 2. 특정한 영역을 임계 영역으로 지정 synchronized(객체의 참조변수) { // 임계 영역(critical section) //... }
첫 번째 방법은 메서드 앞에 synchronized를 붙이는 것인데 synchronized를 붙이면 메서드 전체가 임계 영역으로 설정된다. 쓰레드는 synchronized 메서드가 호출된 시점부터 해당 메서드가 포함된 객체의 lock을 얻어 작업을 수행하다가 메서드가 종료되면 lock을 반환한다.
두 번째 방법은 메서드 내의 코드 일부를 블럭{}으로 감싸고 블럭 앞에 ‘synchronized(참조변수)’를 붙이는 것인데, 이때 참조변수는 락(lock)을 걸고자하는 객체를 참조하는 것이어야 한다. 이 블럭을 synchronized 블럭이라고 부르며 이 블럭의 영역 안으로 들어가면서부터 쓰레드는 지정된 객체의 lock을 얻게 되고, 이 블락을 벗어나면 lock을 반납한다.
두 방법 모두 lock의 획득과 반납이 자동적으로 이루어지므로 우리가 해야 할 일은 그저 임계 영역만 설정해주는 것뿐이다.
모든 객체는 lock을 하나씩 가지고 있으며, 해당 객체의 lock을 가지고 있는 쓰레드만 임계영역의 코드를 수행할 수 있다. 그리고 다른 쓰레드들은 lock을 얻을 때까지 기다리게 된다.
임계 영역은 멀티쓰레드 프로그램의 성능을 좌우하기 때문에 가능하면 메서드 전체에 락을 거는 것보다 synchronized블럭으로 임계 영역을 최소화해서 보다 효율적인 프로그램이 되도록 해야 한다.
임계영역이 많을 수록 성능이 떨어진다.
why? 멀티 쓰레드의 장점이 여러 쓰레드가 동시에 돌아가는 것인데, 임계 영역에서는 한 번에 한 개의 쓰레드만 임계 영역에 들어갈 수 있기 때문이다.
→ 임계영역의 개수 최소화, 영역도 좁아야 한다.
public synchronized void withdraw(int money) {
if (balance >= money) {
try {
Thread.sleep(1000);
} catch (Excepton e) {}
balance -= money;
}
}
↔
public void withdraw(int money) {
synchronized(this) {
if (balance >= money) {
try {
Thread.sleep(1000);
} catch (Exception e) {}
balance -= money;
}
} // synchronized
}
13-32 synchronized를 이용한 동기화 예제1
package Test;
class Main {
public static void main(String args[]) {
Runnable r = new RunnableEx12();
new Thread(r).start();
new Thread(r).start();
} // main
}
class Account {
private int balance = 1000; // private으로 해야 동기화가 의미가 있다.
public int getBalance() {
return balance;
}
public void withdraw(int money) {
if (balance >= money) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
balance -= money;
}
} // withdraw
}
class RunnableEx12 implements Runnable {
Account acc = new Account();
@Override
public void run() {
while (acc.getBalance() > 0) {
// 100, 200, 300중의 한 값을 임의로 선택해서 출금(withdraw)
int money = (int) (Math.random() * 3 + 1) * 100;
acc.withdraw(money);
System.out.println("balance : " + acc.getBalance() + Thread.currentThread().getName());
}
} // run()
}
balance : 500Thread-0
balance : 500Thread-1
balance : 300Thread-0
balance : 200Thread-1
balance : 0Thread-0
balance : -100Thread-1
은행계좌(account)에서 잔고(balance)를 확인하고 임의의 금액을 출금(withdraw)하는 예제인데, 아래를 보면 잔고가 출금하려는 금액보다 큰 경우에만 출금하도록 되어있다.
public void withdraw(int money) {
if (balance >= money) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
balance -= money;
}
} // withdraw
그러나 실행결과를 보면 잔고(balance)가 음수가 되는 것을 볼 수 있는데, 그 이유는 한 쓰레드가 if문의 조건식을 통화하고 출금하기 바로 직전에 다른 쓰레드가 끼어들어서 출금을 먼저 했기 때문이다.
13-33 synchronized를 이용한 동기화 예제2
package Test;
class Main {
public static void main(String args[]) {
Runnable r = new RunnableEx12();
new Thread(r).start();
new Thread(r).start();
} // main
}
class Account {
private int balance = 1000; // private으로 해야 동기화가 의미가 있다.
public int getBalance() {
return balance;
}
public synchronized void withdraw(int money) { // synchronized로 메서드를 동기화
if (balance >= money) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
balance -= money;
}
} // withdraw
}
class RunnableEx12 implements Runnable {
Account acc = new Account();
@Override
public void run() {
while (acc.getBalance() > 0) {
// 100, 200, 300중의 한 값을 임의로 선택해서 출금(withdraw)
int money = (int) (Math.random() * 3 + 1) * 100;
acc.withdraw(money);
System.out.println("balance : " + acc.getBalance() + Thread.currentThread().getName());
}
} // run()
}
이전 예제에 withdraw()에 그저 synchronized만 붙였을 뿐인데도, 전과 달리 결과에 음수값이 나타나지 않는 것을 확인할 수 있다. 여기서 한 가지 주의할 점은 Account 클래스의 인스턴스변수인 balance의 접근 제어자가 private이라는 것이다. 만일 private이 아니면, 외부에서 직접 접근할 수 있기 때문에 아무리 동기화를 해도 이 값의 변경을 막을 길이 없다. synchronized를 이용한 동기화는 지정된 영역의 코드를 한 번에 하나의 쓰레드가 수행하는 것을 보장하는 것일 뿐이기 때문이다.
13-34 wait()과 notify()
- 동기화의 효율을 높이기 위해 wait(), notify()를 사용
- Obejct 클래스에 정의되어 있으며, 동기화 블록 내에서만 사용할 수 있다.
- wait() - 객체의 lock을 풀고 쓰레드의 해당 객체의 waiting pool에 넣는다.
- notify() - waitting pool에서 대기중인 쓰레드 중의 하나를 깨운다.
- notifyAll() - waiting pool에서 대기중인 모든 쓰레드를 깨운다.
balance : 900Thread-0
balance : 700Thread-1
balance : 400Thread-0
balance : 300Thread-1
balance : 200Thread-1
balance : 200Thread-0
balance : 0Thread-1
balance : 0Thread-0

① 계좌에 출금할 돈이 부족해서 한 쓰레드가 락을 보유한 채로 돈이 입금될 때까지 오랜 시간을 보낸다면, 다른 쓰레드들은 모두 해당 객체의 락을 기다리느라 다른 작업들도 원활이 진행되지 않을 것이다. 이러한 상황을 개선하기 위해 고안된 것이 바로 wait()과 notify()이다.
② 동기화된 임계 영역의 코드를 수행하다가 작업을 더 이상 진행할 상황이 아니면, 일단 wait()을 호출하여 쓰레드가 락을 반납하고 기다리게 한다.
③ 그러면 다른 쓰레드가 락을 얻어 해당 객체에 대한 작업을 수행할 수 있게 된다. 나중에 작업을 진행할 수 있는 상황이 되면 notify()를 호출해서, 작업을 중단했던 쓰레드가 다시 락을 얻어 작업을 진행할 수 있게 한다.
※ 오래 기다린 쓰레드가 락을 얻는다는 보장이 없음. notify()가 호출되면, 해당 객체의 대기실에 있던 모든 쓰레드 중에서 임의의 쓰레드만 통지를 받는다.
예제1
- 요리사는 Table에 음식을 추가. 손님은 Table의 음식을 소비
- 요리사와 손님이 같은 객체(Table)을 공유하므로 동기화가 필요
private ArrayList<String> dishes = new ArrayList<>();
public void add(String dish) { // synchronized를 추가
if(dishes.size() >= MAX_FOOD)
return;
dishes.add(dish);
System.out.println("Dishes:" + dishes.toString());
}
public boolean remove(String dishName) {
while(dishes.size()==0) {
String name = Thread.currentThread().getName();
System.out.println(name+" is waiting.");
try { Thread.sleep(500);} catch(InterruptedException e) {}
}
for(int i=0; i<dishes.size();i++)
if(dishName.equals(dishes.get(i))) {
dishes.remove(i);
return true;
}
return false;
}
public void run() {
while(true) {
int idx = (int)(Math.random()*table.dishNum());
table.add(table.dishNames[idx]);
try { Thread.sleep(100);} catch(InterruptedException e) {}
} // while
}
public void run() {
while(true) {
try { Thread.sleep(10);} catch(InterruptedException e) {}
String name = Thread.currentThread().getName();
if(eatFood())
System.out.println(name + " ate a " + food);
else
System.out.println(name + " failed to eat. :(");
} // while
}
예제1 실행결과(동기화 X)
[예외1] 요리사가 Table에 요리를 추가하는 과정에 손님이 요리를 먹음
[예외2] 하나 남은 요리를 손님2가 먹으려하는데, 손님1이 먹음.
- 손님2가 하나 남은 요리를 먹으려고 하는데 자기 차례가 끝나서 못 먹음 → 손님 1이 와서 그걸 홀라당 먹어버림 → 손님2는 있지도 않은 요리를 먹게된다 → 에러 발생
ConcurrentModifyException : ArrayList를 읽기 수행 중에 변경(add(), remove())이 발생하면 나는 에러
[문제점] Table을 여러 쓰레드가 공유하기 때문에 작업 중에 끼어들기 발생
[해결책] Table의 add()와 remove()를 synchronized로 동기화
private ArrayList<String> dishes = new ArrayList<>();
public void add(String dish) {
if(dishes.size() >= MAX_FOOD)
return;
dishes.add(dish);
System.out.println("Dishes:" + dishes.toString());
}
public boolean remove(String dishName) {
while(dishes.size()==0) {
String name = Thread.currentThread().getName();
System.out.println(name+" is waiting.");
try { Thread.sleep(500);} catch(InterruptedException e) {}
}
for(int i=0; i<dishes.size();i++)
if(dishName.equals(dishes.get(i))) {
dishes.remove(i);
return true;
}
return false;
}
→
public synchronized void add(String dish) { // synchronized를 추가
if(dishes.size() >= MAX_FOOD)
return;
dishes.add(dish);
System.out.println("Dishes:" + dishes.toString());
}
public boolean remove(String dishName) {
synchronized(this) {
while(dishes.size()==0) {
String name = Thread.currentThread().getName();
System.out.println(name+" is waiting.");
try { Thread.sleep(500);} catch(InterruptedException e) {}
}
for(int i=0; i<dishes.size();i++)
if(dishName.equals(dishes.get(i))) {
dishes.remove(i);
return true;
}
}
return false;
}
예제1 실행결과(동기화 O)
[문제] 예외는 발생하지 않지만, 손님(CUST2)이 Table에 lock건 상태를 지속, 요리사가 Table의 lock을 얻을 수 없어서 음식을 추가하지 못함
→ 작업이 진행 안 된다. 동기화를 건 것은 좋은데 비효율적임. 이럴 때 wait() & notify()를 쓰면 됨
예제2
[문제점] 음식이 없을 때, 손님이 Table의 lock을 쥐고 안 놓는다.
요리사가 lock을 얻지 못해서 Table에 음식을 추가할 수 없다.
[해결책] 음식이 없을 때, wait()으로 손님이 lock을 풀고 기다리게하자.
요리사가 음식을 추가하면, notify()로 손님에게 알리자.(손님이 lock을 재획득)

예제2 - 실행결과
- 전과 달리 한 쓰레드가 lock을 오래 쥐는 일이 없어짐. 효율적이 됨!!!
but, wait()과 notify()가 호출되는 대상이 불분명함 → 단점을 해소한 것이 lock&condition.
예제 13-15
import java.util.ArrayList; class Customer2 implements Runnable { private Table2 table; private String food; Customer2(Table2 table, String food) { this.table = table; this.food = food; } public void run() { while(true) { try { Thread.sleep(100);} catch(InterruptedException e) {} String name = Thread.currentThread().getName(); table.remove(food); System.out.println(name + " ate a " + food); } // while } } class Cook2 implements Runnable { private Table2 table; Cook2(Table2 table) { this.table = table; } public void run() { while(true) { int idx = (int)(Math.random()*table.dishNum()); table.add(table.dishNames[idx]); try { Thread.sleep(10);} catch(InterruptedException e) {} } // while } } class Table2 { String[] dishNames = { "donut","donut","burger" }; // donut의 확률을 높인다. final int MAX_FOOD = 6; private ArrayList<String> dishes = new ArrayList<>(); public synchronized void add(String dish) { while(dishes.size() >= MAX_FOOD) { String name = Thread.currentThread().getName(); System.out.println(name+" is waiting."); try { wait(); // COOK쓰레드를 기다리게 한다. Thread.sleep(500); } catch(InterruptedException e) {} } dishes.add(dish); notify(); // 기다리고 있는 CUST를 깨우기 위함. System.out.println("Dishes:" + dishes.toString()); } public void remove(String dishName) { synchronized(this) { String name = Thread.currentThread().getName(); while(dishes.size()==0) { System.out.println(name+" is waiting."); try { wait(); // CUST쓰레드를 기다리게 한다. Thread.sleep(500); } catch(InterruptedException e) {} } while(true) { for(int i=0; i<dishes.size();i++) { if(dishName.equals(dishes.get(i))) { dishes.remove(i); notify(); // 잠자고 있는 COOK을 깨우기 위함 return; } } // for문의 끝 try { System.out.println(name+" is waiting."); wait(); // 원하는 음식이 없는 CUST쓰레드를 기다리게 한다. Thread.sleep(500); } catch(InterruptedException e) {} } // while(true) } // synchronized } public int dishNum() { return dishNames.length; } } class Ex13_15 { public static void main(String[] args) throws Exception { Table2 table = new Table2(); new Thread(new Cook2(table), "COOK").start(); new Thread(new Customer2(table, "donut"), "CUST1").start(); new Thread(new Customer2(table, "burger"), "CUST2").start(); Thread.sleep(2000); System.exit(0); } }
🌞13장 연습문제(기초편)
🔵 13-1
package practice13;
public class Excercies13_1 {
public static void main(String[] args) {
Thread th1 = new Thread(new Thread1());
th1.start();
}
}
class Thread1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 300; i++) {
System.out.print("-");
}
}
}
🔵 13-2
답 : 2. resume(), 6. notify()
❌ 13-3
답 : 1번
정답 : 2번
→ t1.start()가 아니라 run()이었음.. 문제 잘 읽자
[해설] Thread2 클래스의 인스턴스를 생성하긴 했지만, start()가 아닌 run()을 호출함으로써 쓰레드를 실행시킨 것이 아니라 단순히 Thread2 클래스의 메서드를 호출한 셈이 되었다. 만일 run()이 아닌 start()를 호출하였다면, 숫자가 섞여서 출력되었을 것이다.
❌ 13-4
답 : 3, 4번
정답 : 4번
[해설] suspend()를 제외한 나머지 메서드들은 interrupt()가 호출되면 InterruptedException이 발생하여 일시정지 상태에서 벗어나 실행대기 상태가 된다.
→ wait()은 notify()로만 깨울 수 있는 줄 알았음 wait()도 interrupt()를 호출하면 일시정지 상태에서 벗어나 실행대기 상태가 된다.
🔵 13-5
package practice13;
public class Excercise13_5 {
static boolean stopped = false;
public static void main(String[] args) {
Thread5 th1 = new Thread5();
th1.start();
try {
Thread.sleep(7 * 1000); // 7초
} catch (Exception e) {}
th1.stopped(); // 쓰레드를 정지시킨다.
System.out.println("stopped");
}
}
class Thread5 extends Thread {
@Override
public void run() {
for (int i = 0; !Excercise13_5.stopped; i++) {
System.out.println(i);
try {
Thread.sleep(3 * 1000);
} catch (Exception e) {}
}
}
public void stopped() {
Excercise13_5.stopped = true;
interrupt();
}
}
🌞13장 연습문제(3판)
🔵 13-1
package ch13.exercise;
public class Exercise13_1 {
public static void main(String[] args) {
// Thread th1 = new Thread1();
Thread th1 = new Thread(new Thread2());
th1.start();
}
}
class Thread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 300; i++) {
System.out.print('-');
}
}
}
class Thread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 300; i++) {
System.out.print('-');
}
}
}
🔵 13-2
답 : 2번
🔵 13-3
답 : a, c, d, e
❌ 13-4
내답 : c, d
정답 : d

📐13-5
답 : 0 1 2 3 4 꽝 5 6 7 8 9
→ main과 th1 두 개의 쓰레드는 별도의 호출스택에서 실행된다. 그래서 main에서 예외가 발생하여 종료되고 호출스택이 없어져도, 쓰레드 th1이 실행되는 호출 스택에는 아무런 영향을 미치지 못한다.
🔵 13-6
답 : 0 1 2 3 4 꽝
❌ 13-7
stopped의 값이 바뀌어도 for문 내의 Thread.sleep(3 * 1000); 문장에 의해 일시정지 상태에 있는 경우, 시간이 끝날 때까지 for문을 벗어날 수 없기 때문에 그렇다.
그래서 interrup()를 호출해서 자고 있는 쓰레드를 깨워야 즉시 정지하게 된다.
🔵 13-8
while (true) {
words.add(data[(int) (Math.random() * data.length)]);
try {
Thread.sleep(interval);
} catch (InterruptedException e) { }
}
❌ 13-9
내 답
package ch13.exercise; import javax.swing.*; public class Exercise13_9 { public static void main(String[] args) { Exercise13_9_1 th1 = new Exercise13_9_1(); th1.setDaemon(true); th1.start(); String input = JOptionPane.showInputDialog("아무 값이나 입력하세요"); System.out.println("입력하신 값은 " + input + "입니다."); th1.interrupt(); } } class Exercise13_9_1 extends Thread { @Override public void run() { int i = 10; while (i != 0 && !isInterrupted()) { System.out.println(i--); try { Thread.sleep(1000); } catch (InterruptedException e) { } System.out.println("카운트가 종료되었습니다."); } } }
정답
package ch13.exercise; import javax.swing.*; public class Exercise13_9 { public static void main(String[] args) { Exercise13_9_1 th1 = new Exercise13_9_1(); th1.start(); String input = JOptionPane.showInputDialog("아무 값이나 입력하세요"); System.out.println("입력하신 값은 " + input + "입니다."); th1.interrupt(); } } class Exercise13_9_1 extends Thread { @Override public void run() { int i = 10; while (i != 0 && !isInterrupted()) { System.out.println(i--); try { Thread.sleep(1000); } catch (InterruptedException e) { interrupt(); } System.out.println("카운트가 종료되었습니다."); } } }
❌
Uploaded by N2T