자바칩
[자바의 신] 25장. 쓰레드 (Thread): 문제 풀이 본문
정리해 봅시다
1. 쓰레드와 프로세스의 차이를 이야기해 보세요.
클래스를 실행하면 적어도 하나의 JVM이 시작되고, 자바 프로세스가 시작한다.
이 프로세스 안에서는 여러개의 쓰레드들이 수행된다. 어떤 프로세스든 간에 쓰레드가 하나 이상 수행된다.
*쓰레드를 만든 이유
프로세스가 하나 시작하려면 많은 자원이 필요하다. 만약 하나의 작업을 동시에 수행하려고 할 때 여러 개의 프로세스를 띄워서 실행하면 각각 메모리를 할당해 주어야만 한다. JVM은 기본적으로 아무런 옵션 없이 실행하면 OS마다 다르지만, 적어도 32MB~64MB의 물리 메모리를 점유한다. 그에 반해서, 쓰레드를 하나 추가하면 1MB 이내의 메모리를 점유한다. 그래서, 쓰레드를 "경량 프로세스(lightweight process)"라고도 부른다.
그리고, 요즘은 PC 급의 장비도 두 개 이상의 코어(core)가 달려있는 멀티 코어 시대다. 대부분의 작업은 단일 쓰레드로 실행하는 것보다는 다중 쓰레드로 실행하는 것이 더 빠른 시간에 결과를 제공해준다. 따라서, 보다 빠른 처리를 할 필요가 있을 때, 쓰레드를 사용하면 보다 빠른 계산을 처리할 수도 있다.
2. 여러분들이 쓰레드 클래스를 만들기 위해서는 어떤 인터페이스를 구현하면 될까요?
Runnable 인터페이스
3. 2에서 이야기한 인터페이스에 선언되어 있는 유일한 메소드는 무엇인가요?
run()
쓰레드가 시작되면 수행되는 메소드이다.
Runnable 인터페이스를 구현하는 클래스에서 run() 메소드를 구현해주어야 한다.
*사용 예시
public class MyThread implements Runnable {
public void run() {
System.out.println("Thread starts.");
}
}
4. 쓰레드 클래스를 만들기 위해서 어떤 클래스를 확장하면 되나요?
Thread 클래스
5. 쓰레드가 시작되는 메소드의 이름은 무엇인가요?
run()
*사용 예시
public class MyThread extends Thread {
public void run() {
System.out.println("Thread starts.");
}
}
6. 쓰레드를 시작하는 메소드의 이름은 무엇인가요?
start()
*사용 예시
MyThread thread = new MyThread();
thread.start(); // thread 객체의 run() 메소드 실행
7. 쓰레드에 선언되어 있는 sleep() 메소드의 역할은 무엇인가요?
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
static void | sleep(long millis) | 매개 변수로 넘어온 시간(1/1,000초)만큼 대기한다. |
static void | sleep(long millis, int nanos) | 첫 번째 매개 변수로 넘어온 시간(1/1,000초) + 두 번째 매개 변수로 넘어온 시간(1/1,000,000,000초)만큼 대기한다. |
*사용 예시
try {
MyThread thread = new MyThread();
thread.start();
thread.sleep(1000); // 쓰레드를 1초 대기시킴
} catch (InterruptedException ie) {
// sleep() 메소드는 InterruptedException을 발생시킬 수 있으므로 예외 처리가 반드시 필요
ie.printStackTrace();
}
8. sleep() 메소드를 사용할 때에는 try-catch로 감싸 주어 예외를 처리해 주어야 하는데, 그 이유는 무엇인가요?
sleep() 메소드는 InterruptedException을 발생시킬 수도 있기 때문이다.
InterruptedException이란, 실행 중인 쓰레드를 강제로 종료시킬 때 발생하는 예외이다.
추가로 InterruptedException을 발생시킬 수 있는 메소드에는 join(), wait(), interrupt()가 있다.
9. 데몬(Daemon) 쓰레드와 일반 쓰레드의 차이는 무엇인가요?
쓰레드 종류 | 차이점 |
데몬(Daemon) 쓰레드 | - run() 메소드가 끝나지 않아도 다른 수행중인 쓰레드가 아무것도 없으면, 종료되는 쓰레드. 데몬 쓰레드가 종료되면 프로세스도 종료됨 - setDaemon() 매개변수로 true를 넘겨주면 데몬 쓰레드로 지정이 됨. 반드시 쓰레드가 시작되기 전에 지정해주어야 함. 그렇지 않으면 데몬 쓰레드로 지정을 할 수 없음 |
일반 쓰레드 | - run() 메소드가 끝나지 않으면 종료되지 않는 쓰레드 - 프로세스의 쓰레드가 하나라도 살아 있으면, 프로세스도 끝나지 않고 계속 실행됨 - setDaemon() 매개변수로 false를 넘겨주면 일반 쓰레드로 지정이 됨. |
*데몬 쓰레드 지정 방법
Thread daemonThread = new Thread();
daemonThread.setDaemon(true);
10.synchronized 구문은 왜 써주며, 어디에 사용해야 하나요?
synchronized 구문은 한 객체에 선언되어있는 인스턴스 변수를 조작하는 메소드에 여러 쓰레드가 접근했을 때, 동시에 연산을 수행하여 값이 꼬이는 문제를 방지하기 위해 사용한다.
*사용 방법
다음과 같이 연산을 처리하는 클래스가 있다.
이 클래스에는 인스턴스 변수를 조작하는 메소드가 2개 선언되어있다.
파란색으로 표시한 메소드를 여러 쓰레드에서 동시에 접근하려고 하면 연산을 동시에 처리하려고 하여 값이 꼬일 수가 있다. 이런 문제점을 방지하기 위해 synchronized로 선언하여 thread safe하게 만들어야 한다.
public class Calculate {
private int amount = 0;
public void add(int value) {
amount += value;
}
public void subtract(int value) {
amount -= value;
}
}
1. 메소드 자체를 synchronized로 선언하는 방법 (synchronized methods)
public synchronized void add() {
amount += value;
}
public synchronized void subtract() {
amount -= value;
}
하지만 이렇게 하면, 성능상 문제점이 발생할 수 있다. 예를 들어 어떤 클래스에 30줄짜리 메소드에서 amount라는 변수를 한 줄에서만 다룬다. 만약 해당 메소드 전체를 synchronized로 선언한다면, 나머지 29줄의 처리를 할 때 필요 없는 대기 시간이 발생하게 된다. 이러한 경우에는 메소드 전체를 감싸면 안 되며, amount라는 변수를 처리하는 부분만 synchronized 처리를 해 주면 된다.
2. 메소드 내의 특정 문장만 synchronized로 감싸는 방법 (synchronized statements)
Object lock = new Object(); // 잠금 처리를 하기 위한 객체
public void add() {
synchronized(lock) {
amount += value;
}
}
public void subtract() {
synchronized(lock) {
amount -= value;
}
}
11. synchronized를 사용하는 두 가지 방법은 어떤 것인가요?
10번 답 참고
12. 쓰레드의 상태에는 어떤 것들이 있나요?
상태 | 의미 |
NEW | 쓰레드 객체는 생성되었지만, 아직 시작되지는 않은 상태 |
RUNNABLE | 쓰레드가 실행 중인 상태 |
BLOCKED | 쓰레드가 실행 중지 상태이며, 모니터 락(monitor lock)이 풀리기를 기다리는 상태 |
WAITING | 쓰레드가 대기 중인 상태 |
TIMED-WAITING | 특정 시간만큼 쓰레드가 대기 중인 상태 |
TERMINATED | 쓰레드가 종료된 상태 |
Thread 클래스에는 State라는 enum 클래스가 있다. 이 클래스는 public static으로 선언되어 있다.
getState() 메소드를 사용하여 쓰레드의 상태를 확인할 수 있다. 이 메소드의 리턴 타입은 Thread.State이다.
어떤 쓰레드이건 간에 "NEW -> 상태 -> TERMINATED"의 라이프 사이클을 가진다. 여기서 "상태"에 해당하는 것은 NEW와 TERMINATED를 제외한 모든 다른 상태를 의미한다.
13. 쓰레드에 선언되어 있는 join() 메소드의 용도는 무엇인가요?
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
void | join() | 수행 중인 쓰레드가 중지할 때까지 대기한다. |
void | join(long millis) | 매개 변수에 지정된 시간만큼(1/1,000초) 대기한다. |
void | join(long millis, int nanos) | 첫 번째 매개 변수에 지정된 시간(1/1,000초) + 두 번째 매개 변수에 지정된 시간(1/1,000,000,000초)만큼 대기한다. 두 번째 매개 변수인 나노초의 값은 0~999,999 사이의 값만 지정할 수 있다. |
*사용 예시
try {
Thread thread = new Thread();
thread.start();
thread.join(); // 수행 중인 쓰레드가 중지할 때까지 대기
System.out.println("쓰레드가 정상적으로 종료됨");
} catch (InterruptedException ie) {
// join() 메소드는 InterruptedException을 발생시킬 수 있으므로 예외 처리가 반드시 필요
ie.printStackTrace();
}
14. 쓰레드에 선언되어 있는 interrupt() 메소드의 용도는 무엇인가요?
수행 중인 쓰레드에 중지 요청을 한다. 쓰레드가 종료되지 않았는데 interrupt() 메소드를 호출하면, InterruptedException 예외를 발생시키고 쓰레드를 종료한다. 하지만 아직 시작되지 않은 쓰레드나 종료된 쓰레드에 interrupt() 메소드를 호출하면 아무런 예외나 에러를 발생시키지 않고 다음 문장을 수행한다.
*사용 예시
try {
Thread thread = new Thread();
thread.start();
thread.interrupt(); // 수행 중인 쓰레드에 중지 요청
System.out.println("쓰레드가 정상적으로 종료됨");
} catch (InterruptedException ie) {
// interrupt() 메소드는 InterruptedException을 발생시킬 수 있으므로 예외 처리가 반드시 필요
ie.printStackTrace();
}
15. interrupt() 메소드를 호출하면 해당 쓰레드는 어떤 상태에 있을 때 interrupt() 메소드가 호출된 효과가 발생되나요?
쓰레드가 종료되지 않은 상태(RUNNABLE, BLOCKED, WAITING, TIMED-WAITING)일 때 interrupt() 메소드를 호출하면, InterruptedException 예외를 발생시키고 쓰레드를 종료한다. 그 외의 상태(NEW, TERMINATED)일 때에는 예외를 발생시키지 않는다.
16. Object 클래스에 선언된 wait() 메소드의 용도는 무엇인가요?
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
void | wait() | 다른 쓰레드가 Object 객체에 대한 notify() 메소드나 notifyAll() 메소드를 호출할 때까지 현재 쓰레드가 대기하고 있도록 한다. |
void | wait(long timeout) | wait() 메소드와 동일한 기능을 제공하며, 매개 변수에 지정한 시간만큼만 대기한다. 즉, 매개 변수 시간을 넘어 섰을 때에는 현재 쓰레드는 다시 깨어난다. 여기서의 시간은 밀리초로 1/1,000초 단위다. 만약 1초간 기다리게 할 경우에는 1000을 매개 변수로 넘겨주면 된다. |
void | wait(long timeout, int nanos) | wait() 메소드와 동일한 기능을 제공한다. 하지만, wait(timeout)에서 밀리초 단위의 대기 시간을 기다린다면, 이 메소드는 보다 자세한 밀리초+나노초(1/1,000,000,000초)만큼만 대기한다. 뒤에 있는 나노초의 값은 0~999,999 사이의 값만 지정할 수 있다. |
*사용 예시
public class StateThread extends Thread {
private Object monitor;
public StateThread(Object monitor) {
this.monitor = monitor;
}
public void run() {
try {
for (int loop = 0; loop < 10000; loop++) {
String a = "A";
}
synchronized(monitor) {
// 다른 쓰레드가 monitor(Object) 객체에 대한 notify()나 notifyAll() 메소드를 호출할 때까지
// 현재 쓰레드가 대기
monitor.wait(); // monitor(Object) 객체의 모니터에 현재 쓰레드를 대기시킴
}
System.out.println("쓰레드가 깨어남");
} catch (InterruptedException ie) {
// wait() 메소드는 InterruptedException을 발생시킬 수 있으므로 예외 처리가 반드시 필요
ie.printStackTrace();
}
}
}
17. Object 클래스에 선언된 notify() 메소드의 용도는 무엇인가요?
리턴 타입 | 메소드 이름 | 설명 |
void | notify() | Object 객체의 모니터에 대기하고 있는 단일 쓰레드를 깨운다. |
void | notifyAll() | Object 객체의 모니터에 대기하고 있는 모든 쓰레드를 깨운다. |
*사용 예시
public class RunObjectThreads {
public static void main(String[] args) {
RunObjectThreads sample = new RunObjectThreads();
sample.checkThreadState();
}
public void checkThreadState() {
Object monitor = new Object(); // 이 객체(Object)의 모니터에 대기하고 있는 쓰레드를 깨워야 함
StateThread thread = new StateThread(monitor);
try {
thread.start();
synchronized(monitor) {
// notify() 메소드는 먼저 대기하고 있는 1개의 쓰레드만 깨우기 때문에
// 대기 중인 쓰레드를 모두 깨우려면 notifyAll() 메소드 사용
monitor.notify(); // monitor(Object) 객체의 모니터에 대기하고 있는 단일 쓰레드를 꺠움
}
thread.join();
System.out.println("쓰레드 종료");
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
18. ThreadGroup 클래스에 선언된 enumerate() 메소드의 용도는 무엇인가요?
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
int | enumerate(Thread[] list) | 현재 쓰레드 그룹에 있는 모든 쓰레드를 매개 변수로 넘어온 쓰레드 배열에 담는다. |
int | enumerate(Thread[] list, boolean recurse) | 현재 쓰레드 그룹에 있는 모든 쓰레드를 매개 변수로 넘어온 쓰레드 배열에 담는다. 두 번째 매개 변수가 true이면 하위에 있는 쓰레드 그룹에 있는 쓰레드 목록도 포함한다. |
int | enumerate(ThreadGroup[] list) | 현재 쓰레드 그룹에 있는 모든 쓰레드 그룹을 매개 변수로 넘어온 쓰레드 그룹 배열에 담는다. |
int | enumerate(ThreadGroup[] list, boolean recurse) | 현재 쓰레드 그룹에 있는 모든 쓰레드 그룹을 매개 변수로 넘어온 쓰레드 그룹 배열에 담는다. 두 번째 매개 변수가 true이면 하위에 있는 쓰레드 그룹 목록도 포함한다. |
enumerate() 메소드의 리턴값은 배열에 저장된 쓰레드의 개수이다.
직접해 봅시다
'Study > Java' 카테고리의 다른 글
[자바의 신] 27장. Serializable과 NIO: 문제 풀이 (0) | 2024.05.26 |
---|---|
[자바의 신] 26장. I/O: 문제 풀이 (0) | 2024.05.26 |
[Java] 상속과 조합 (0) | 2024.05.20 |
[Java] 가비지 컬렉션 (GC) (0) | 2024.05.19 |
[Java] Java 8과 Java 17 (0) | 2024.05.19 |