[13-1] 쓰레드를 구현하는 방법에는 Thread 클래스로부터 상속받는 것과 Runnable 인터페이스를 구현하는 것 두 가지가 있는데, 다음의 코드는 Thread클래스를 상속받아서 쓰레드를 구현한 것이다. 이 코드를 Runnable 인터페이스를 구현하도록 변경하시오.
class Exercise13_1 {
public static void main(String args[]) {
Thread1 th1 = new Thread1();
th1.start();
}
}
class Thread1 extends Thread {
public void run() {
for (int i = 0; i < 300; i++) {
System.out.print('-');
}
}
}
답
public class Exercise13_1 {
public static void main(String args[]) {
Thread1 th1 = new Thread1();
Thread thread = new Thread(th1);
thread.start();
}
}
// Runnable 인터페이스 구현
class Thread1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 300; i++) {
System.out.print('-');
}
}
}
참고
방법1) Thread 클래스 상속
- 장점
- Thread 클래스를 상속하는 것이 Runnable 인터페이스를 구현하는 것보다 쉬움
- run() 메서드를 오버라이딩해서 스레드가 실행할 코드를 작성
- 단점
- 자바는 단일 상속만 지원하므로 이미 다른 클래스를 상속받고 있다면 Thread를 상속받을 수 없음
- 유연성이 떨어짐
방법2) Runnable 클래스 상속
- 장점
- 다른 클래스를 상속받고 있는 경우에도 Runnable 인터페이스를 구현하여 스레드를 만들 수 있음
- 유연성이 높고 객체 지향적인 설계가 가능
- Runnable 인터페이스를 구현한 클래스는 스레드로 사용될 필요가 없는 경우에도 독립적으로 사용할 수 있음
- 단점
- Thread 객체에 Runnable 객체를 전달해야 함
[13-2] 다음 코드의 실행결과로 옳은 것은?
class Exercise13_2 {
public static void main(String[] args) {
Thread2 t1 = new Thread2();
t1.run();
for (int i = 0; i < 10; i++)
System.out.print(i);
}
}
class Thread2 extends Thread {
public void run() {
for (int i = 0; i < 10; i++)
System.out.print(i);
}
}
a. 01021233454567689789처럼 0부터 9까지의 숫자가 섞여서 출력된다.
b. 012345678901234567890처럼 0부터 9까지의 숫자가 순서대로 출력된다.
c. IllegalThreadStateException이 발생한다.
답
b
01234567890123456789
Process finished with exit code 0
풀이
a. 01021233454567689789처럼 0부터 9까지의 숫자가 섞여서 출력된다.
틀림. 이 결과는 두 스레드가 동시에 실행될 때 나올 수 있는 결과.
b. 012345678901234567890처럼 0부터 9까지의 숫자가 순서대로 출력된다.
맞음. run() 메서드를 단순히 호출했으므로 순차적으로 실행됨.
c. IllegalThreadStateException이 발생한다.
틀림. 이 예외는 스레드 상태와 관련된 예외로, 이 코드에서는 발생하지 않음.
[13-3] 다음 중 쓰레드를 일시정지 상태(WAITING)로 만드는 것이 아닌 것은?(모두 고르시오)
a. suspend( )
b. resume( )
c. join( )
d. sleep( )
e. wait( )
f. notify( )
답
b,f
풀이
a. suspend()
쓰레드를 일시정지 상태로 만듦. 하지만, suspend()는 교착 상태(deadlock)를 유발할 수 있어 현재는 사용하지 않음.
b. resume()
일시정지 상태의 쓰레드를 다시 실행 대기 상태로 만듦. 일시정지 상태로 만들지 않음.
c. join()
다른 쓰레드가 종료될 때까지 현재 쓰레드를 일시정지 상태로 만듦. 일시정지 상태로 만듦.
d. sleep()
일정 시간 동안 현재 쓰레드를 일시정지 상태로 만듦. 일시정지 상태로 만듦.
e. wait()
다른 쓰레드가 notify()나 notifyAll()을 호출할 때까지 현재 쓰레드를 일시정지 상태로 만듦. 일시정지 상태로 만듦.
f. notify()
일시정지 상태의 쓰레드를 다시 실행 대기 상태로 만듦. 일시정지 상태로 만들지 않음.
[13-4] 다음 중 interrupt( )에 의해서 실행대기 상태(RUNNABLE)가 되지 않는 경우는? (모두 고르시오)
a. sleep( )에 의해서 일시정지 상태인 쓰레드
b. join( )에 의해서 일시정지 상태인 쓰레드
c. wait( )에 의해서 일시정지 상태인 쓰레드
d. suspend( )에 의해서 일시정지 상태인 쓰레드
답
d
풀이
suspend()를 제외한 나머지 메서드들은 interrupt()가 호출되면 InterruptedException이 발생, 일시정지 상태에서 벗어나 실행대기 상태가 된다. try-catch문으로 InterrupException을 처리해주어야 한다.
a. sleep()에 의해서 일시정지 상태인 쓰레드
interrupt()가 호출되면 InterruptedException이 발생하고, 쓰레드는 실행대기 상태로 전환
b. join()에 의해서 일시정지 상태인 쓰레드
interrupt()가 호출되면 InterruptedException이 발생하고, 쓰레드는 실행대기 상태로 전환
c. wait()에 의해서 일시정지 상태인 쓰레드
interrupt()가 호출되면 InterruptedException이 발생하고, 쓰레드는 실행대기 상태로 전환
d. suspend()에 의해서 일시정지 상태인 쓰레드
suspend()는 교착 상태(deadlock)를 유발할 수 있어 현재는 사용되지 않습니다. interrupt()가 호출되더라도 suspend() 상태의 쓰레드는 실행대기 상태로 전환되지 않습니다.
[13-5] 다음의 코드를 실행한 결과를 예측하고, 직접 실행한 결과와 비교하라. 만일 예측한 결과와 실행한 결과의 차이가 있다면 그 이유를 설명하라.
class Exercise13_5 {
public static void main(String[] args) throws Exception {
Thread3 th1 = new Thread3();
th1.start();
try {
Thread.sleep(5 * 1000);
} catch (Exception e) {
}
throw new Exception("꽝 ~!!!");
}
}
class Thread3 extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (Exception e) {
}
}
}
}
답
예측 : 0-9까지 돌고 한꺼번에 6초 쉬고 꽝 출력
실행 :
0
1
2
3
4
Exception in thread "main" java.lang.Exception: 꽝 ~!!!
at Exercise13_5.main(Exercise13_5.java:11)
5
6
7
8
9
Process finished with exit code 1
예측과 실행 결과가 다른 이유 :
- th1 스레드가 0부터 4까지 출력.
- 메인 스레드는 5초 동안 일시 정지 후 예외를 던짐.
- 예외 발생으로 인해 th1 스레드는 5 이후의 숫자를 출력하지 못함.
[13-6] 다음의 코드를 실행한 결과를 예측하고, 직접 실행한 결과와 비교하라. 만일 예측한 결과와 실행한 결과의 차이가 있다면 그 이유를 설명하라.
class Exercise13_6 {
public static void main(String[] args) throws Exception {
Thread4 th1 = new Thread4();
th1.setDaemon(true); //쓰레드 th1을 데몬쓰레드로 설정한다.
th1.start();
try {
th1.sleep(5 * 1000);
} catch (Exception e) {
}
throw new Exception("꽝~!!!");
}
}
class Thread4 extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (Exception e) {
}
}
}
}
답
예측 : setDaemon(true) 때문에 스레드가 실행되지 않아 바로 꽝 출력
실행 :
0
1
2
3
4
Exception in thread "main" java.lang.Exception: 꽝~!!!
at Exercise13_6.main(Exercise13_6.java:11)
Process finished with exit code 1
예측과 실행 결과가 다른 이유 :
데몬 스레드는 메인 메서드가 종료되면 자동으로 종료된다.
그러나 메인 스레드가 실행되는 동안에 데몬 스레드는 정상적으로 실행된다.
(= 데몬 스레드는 메인 스레드를 보조 하는 역할이므로 메인 스레드 진행에 영향을 줄 수 없음)
로직 진행 순서
1. th1이 데몬 스레드로 설정됨.
2. th1이 시작되어 run() 메서드 실행 시작.
3. 메인 스레드는 5초 동안 일시 정지 (Thread.sleep(5 * 1000)).
4. 메인 스레드는 일시 정지 후 Exception("꽝~!!!")을 던져 종료됨.
5. 메인 스레드가 종료되면 데몬 스레드 th1도 종료됨.
[13-7] 다음의 코드는 쓰레드 th1을 생성해서 실행시킨 다음 6초 후에 정지시키는 코드이다. 그러나 실제로 실행시켜보면 쓰레드를 정지시킨 다음에도 몇 초가 지난 후에서야 멈춘다. 그 이유를 설명하고, 쓰레드를 정지시키면 지체없이 바로 정지되도록 코드를 개선하시오.
0
1
2
stopped
0
1
stopped
Process finished with exit code 0
답
쓰레드가 정지하는데 몇 초가 걸리는 이유는 Thread.sleep(3 * 1000) 메서드 호출 때문이다. Thread.sleep() 메서드는 쓰레드를 지정된 시간 동안 일시정지 상태로 만든다. stopped 변수를 true로 설정해도, 쓰레드가 sleep() 상태에 있는 동안은 즉시 중단되지 않는다.
쓰레드가 지체 없이 정지되도록 코드를 개선하려면 interrupt() 메서드를 사용하여 sleep() 상태에 있는 쓰레드를 깨우고 예외를 발생시켜서 루프를 빠져나가도록 해야 한다.
class Exercise13_7 {
static volatile boolean stopped = false; // volatile 키워드를 사용해 메모리 가시성 보장
public static void main(String[] args) {
Thread5 th1 = new Thread5();
th1.start();
try {
Thread.sleep(6 * 1000);
} catch (Exception e) {
}
stopped = true; // 쓰레드를 정지시킨다.
th1.interrupt(); // 일시정지 상태에 있는 쓰레드를 깨운다.
System.out.println("stopped");
}
}
class Thread5 extends Thread {
public void run() {
// Exercise13_7.stopped의 값이 false인 동안 반복한다.
for (int i = 0; !Exercise13_7.stopped; i++) {
System.out.println(i);
try {
Thread.sleep(3 * 1000);
} catch (InterruptedException e) {
// InterruptedException 발생 시, 루프를 빠져나가도록 한다.
break;
}
}
}
}
로직 진행 순서
1. stopped 변수를 volatile로 선언하여 쓰레드 간의 메모리 가시성을 보장.
2. stopped 변수를 true로 설정하고 th1.interrupt()를 호출하여 쓰레드가 즉시 깨어나도록 함.
3. Thread5 클래스의 run() 메서드에서 InterruptedException을 처리하여 sleep() 상태에서 깨어날 수 있도록 하고, 예외가 발생하면 루프를 빠져나가도록 함.
[13-8] 다음의 코드는 텍스트기반의 타자연습게임인데 WordGenerator라는 쓰레드가 Vector에 2초마다 단어를 하나씩 추가하고, 사용자가 단어를 입력하면 Vector에서 일치하는 단어를 삭제하도록 되어 있다. WordGenerator의 run( )을 완성하시오.
[]
>>
[서현]
>> 서현
[수영,윤아]
>> 수영
[윤아,유리]
>> 유리
[윤아,티파니]
>> 티파니
[윤아,윤아,유리]
>> 윤아
[윤아,유리]
>> 유리
[윤아,효연]
>> 효연
[윤아,티파니]
>> 윤아
[티파니,윤아]
>> 티파니
[윤아,수영,써니]
>>
답
import java.util.*;
class Exercise13_8 {
Vector<String> words = new Vector<>();
String[] data = { "태연", "유리", "윤아", "효연", "수영", "서현", "티파니", "써니", "제시카" };
int interval = 2 * 1000; // 2초
WordGenerator wg = new WordGenerator();
public static void main(String args[]) {
Exercise13_8 game = new Exercise13_8();
game.wg.start();
Vector<String> words = game.words;
while (true) {
synchronized (words) {
System.out.println(words);
}
String prompt = ">>";
System.out.print(prompt);
// 화면으로부터 라인단위로 입력받는다.
Scanner s = new Scanner(System.in);
String input = s.nextLine().trim();
synchronized (words) {
int index = words.indexOf(input);
if (index != -1) {
words.remove(index);
}
}
}
}
class WordGenerator extends Thread {
public void run() {
while (true) {
int rand = (int) (Math.random() * data.length);
synchronized (words) {
words.add(data[rand]);
}
try {
Thread.sleep(interval);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
참고 : synchronized()
- synchronized (words) 블록을 사용하여 words 벡터에 접근하는 모든 코드를 동기화
메인 스레드에서 System.out.println(words);를 호출할 때.
메인 스레드에서 사용자 입력으로 단어를 벡터에서 제거할 때.
WordGenerator 쓰레드에서 벡터에 단어를 추가할 때.
로직 진행 순서
- 단어 추가
1. WordGenerator 클래스의 run() 메서드는 무한 루프(while (true))를 사용하여 계속해서 단어를 추가.
2. Math.random()을 사용하여 data 배열에서 임의의 인덱스를 선택.
3. 선택된 단어를 words 벡터에 추가.
4. Thread.sleep(interval)을 호출하여 2초 동안 대기.
5. Exception이 발생할 경우 e.printStackTrace()를 호출하여 예외 정보를 출력
- 게임 진행
1. WordGenerator 쓰레드는 2초마다 data 배열에서 임의의 단어를 선택하여 words 벡터에 추가.
2. 메인 스레드는 사용자 입력을 받아 words 벡터에서 일치하는 단어를 삭제.
3. words 벡터의 상태를 계속해서 출력.
[13-9] 다음은 사용자의 입력을 출력하고 종료하는 프로그램을 작성한 것으로, 10초 동안 입력이 없으면 자동종료되어야 한다. 그러나 실행결과를 보면, 사용자의 입력이 10초 안에 이루어졌음에도 불구하고 프로그램이 즉시 종료되지 않는다. 사용자로부터 입력받는 즉시 프로그램이 종료되도록 수정하시오.
사진 설명을 입력하세요.
사진 설명을 입력하세요.
사진 설명을 입력하세요.
답
1. 사용자 입력 시 카운트 다운이 즉시 중지되려면 main 클래스 쓰레드를 인터럽트 하는 코드를 추가
2. Thread.sleep()에서 발생하는 InterrupedException을 제대로 처리하여 카운트다운이 중지되도록
import javax.swing.JOptionPane;
class Exercise13_9 {
public static void main(String[] args) throws Exception {
Exercise13_9_1 th1 = new Exercise13_9_1();
th1.start();
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
System.out.println("입력하신 값은 " + input + "입니다.");
th1.interrupt(); // 쓰레드에게 작업을 멈추라고 요청
// 사용자 입력을 받은 후 즉시 프로그램 종료
System.exit(0);
}
}
class Exercise13_9_1 extends Thread {
public void run() {
int i = 10;
while (i != 0 && !isInterrupted()) {
System.out.println(i--);
try {
Thread.sleep(1000); // 1초 지연
} catch (InterruptedException e) {
// 인터럽트가 발생하면 종료
return;
}
}
System.out.println("카운트가 종료되었습니다.");
}
}
로직 진행 순서
1. Exercise13_9 클래스의 main 메서드에서 사용자 입력을 받은 후 System.exit(0);을 호출하여 프로그램을 즉시 종료
2. Exercise13_9_1 클래스의 run 메서드에서 Thread.sleep(1000)에서 InterruptedException이 발생하면 return을 사용하여 쓰레드를 종료
'개념' 카테고리의 다른 글
[JAVA의 정석] Chapter15_연습문제 (0) | 2024.12.15 |
---|---|
[JAVA의 정석] Chapter14_연습문제 (1) | 2024.12.15 |
[JAVA의 정석] Chapter12_연습문제 (2) | 2024.12.15 |
[JAVA의 정석] Chapter11_연습문제 (0) | 2024.12.15 |
[JAVA의 정석] Chapter10_연습문제 (0) | 2024.12.15 |