Java프로그래밍 13강 - 멀티 스레드 프로그래밍
이번 강의에서는 하나의 프로세스 안에서 여러 실행 흐름을 구성하는 멀티 스레드 프로그래밍을 학습한다. 스레드의 생성과 실행, 상태 전이와 제어 메소드, 공유 자원에서 발생하는 간섭 문제, synchronized를 이용한 동기화까지 실행 흐름을 중심으로 정리한다.
제1장 프로세스와 스레드
1. 프로세스의 의미
자바 프로그램은 하나의 프로세스로 만들어져 실행된다. 프로세스는 프로그램의 동적인 실행 단위이며, 실행에 필요한 자원을 확보하고 실제로 동작 중인 프로그램을 뜻한다. 자바 프로그램이 시작되면 JVM이 초기화되고 main() 메소드가 호출되면서 기본 실행 흐름이 시작된다.
프로세스와 프로그램은 구별해야 한다. 프로그램은 저장 장치에 있는 정적인 코드이고, 프로세스는 그 프로그램이 메모리와 CPU 등의 자원을 사용하며 실행되는 동적인 상태이다.
2. 프로세스 안의 작은 실행 흐름
스레드는 프로세스 내부에 존재하는 소규모 실행 흐름이다. 하나의 프로세스 안에 여러 스레드가 존재할 수 있으며, 각각은 독립적인 실행 순서를 가진다. 스레드는 독립된 프로세스보다 가벼운 실행 단위이므로 흔히 경량 프로세스라고도 한다.
| 구분 | 프로세스 | 스레드 |
|---|---|---|
| 의미 | 자원을 확보하고 실행 중인 프로그램 | 프로세스 내부의 작은 실행 흐름 |
| 관계 | 하나 이상의 스레드를 포함한다. | 하나의 프로세스 안에서 실행된다. |
| 특징 | 프로그램의 동적인 실행 단위이다. | 비교적 가벼우며 공유 자원을 함께 사용할 수 있다. |
시험 핵심: 자바 프로그램은 main 스레드에서 시작하며, 필요에 따라 main 스레드가 새로운 스레드를 만들어 여러 실행 흐름을 구성할 수 있다.
제2장 멀티 스레드의 특징
1. 하나의 프로세스에서 여러 작업 실행
멀티 스레드는 하나의 프로세스 안에 동시에 실행하기 위한 여러 스레드가 존재하는 형태이다. 자바 프로그램은 기본적으로 main 스레드로 시작되며, main 스레드가 추가 스레드를 만들고 실행시킬 수 있다. 스레드가 시작되면 각 실행 흐름은 정해진 단일 순서를 공유하는 것이 아니라 독립적으로 진행된다.
여러 스레드가 실행되는 순서는 운영체제와 JVM의 스케줄링에 영향을 받는다. 따라서 같은 프로그램을 여러 번 실행해도 화면에 출력되는 스레드 이름의 순서가 달라질 수 있으며, 특정 스레드가 반드시 먼저 끝난다고 단정할 수 없다.
2. main 스레드와 다른 스레드의 종료
main 스레드가 다른 스레드를 시작한 뒤 자신의 작업을 먼저 끝낼 수도 있다. 그러나 main 스레드가 끝났다는 사실만으로 다른 실행 중인 스레드가 즉시 종료되는 것은 아니다. 시작된 스레드는 자신의 run() 실행을 계속한 뒤 종료될 수 있다.
멀티 스레드 프로그램의 출력 순서는 실행 환경과 시점에 따라 달라진다. 예제와 출력 순서가 다르더라도 각 스레드가 독립적으로 실행되었다면 잘못된 결과라고 볼 수 없다.
제3장 Thread 클래스
1. 생성자와 실행 대상
Thread 클래스는 스레드를 생성하고 관리하기 위한 생성자와 메소드를 제공한다. Thread()는 새 스레드를 만들고 기본 이름을 부여한다. Thread(String name)은 지정한 이름으로 스레드를 만든다. Thread(Runnable target)은 Runnable 객체의 run()을 실행 대상으로 삼으며, Thread(Runnable target, String name)은 실행 대상과 스레드 이름을 함께 지정한다.
| 생성자 | 설명 |
|---|---|
Thread() | 새 스레드를 만들고 기본 이름을 부여한다. |
Thread(String name) | 지정한 이름으로 새 스레드를 만든다. |
Thread(Runnable target) | target 객체의 run()을 실행할 스레드를 만든다. |
Thread(Runnable target, String name) | 실행 대상과 이름을 모두 지정한다. |
2. 주요 메소드
currentThread()는 현재 실행 중인 스레드의 참조를 반환하는 정적 메소드이다. getName()과 setName()은 스레드 이름을 조회하거나 변경한다. getPriority()와 setPriority()는 우선순위를 조회하거나 지정하며, 높은 우선순위의 스레드는 CPU를 얻을 가능성이 높다. start()는 새 실행 흐름을 시작하여 해당 스레드의 run()이 실행되게 한다.
start()와 run()의 구별: 스레드 객체를 만든 뒤 start()를 호출해야 새로운 실행 흐름이 시작된다. 실행할 코드는 run() 안에 정의한다.
제4장 스레드 생성 방법 1 - Thread 상속
1. Thread의 자식 클래스 정의
첫 번째 방법은 Thread 클래스를 상속하는 클래스를 만들고 run()을 재정의하는 것이다. 실행할 작업을 run() 안에 작성하고, 자식 클래스의 객체를 만든 뒤 start()를 호출한다. Thread를 직접 상속하므로 run() 안에서 getName()과 같은 상속받은 메소드를 바로 사용할 수 있다.
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName());
}
}
}
Thread t1 = new MyThread();
Thread t2 = new MyThread();
t1.start();
t2.start();
System.out.println("main");
이 예제에서는 t1과 t2가 각각 별도의 스레드로 run()을 실행하고, main 스레드도 동시에 자신의 출력 작업을 진행한다. 출력 순서는 매번 달라질 수 있다. 기본 이름을 사용하면 일반적으로 Thread-0, Thread-1과 같은 이름이 보인다.
Thread를 상속하는 방식은 구조가 직관적이지만, 자바 클래스는 하나의 클래스만 상속할 수 있으므로 이미 다른 부모 클래스를 상속해야 하는 경우에는 적용하기 어렵다.
제5장 스레드 생성 방법 2 - Runnable 구현
1. 작업과 스레드 객체 분리
두 번째 방법은 작업을 수행할 클래스가 Runnable 인터페이스를 구현하고 run()을 정의하는 것이다. Runnable 객체 자체는 스레드가 아니므로 이를 Thread 생성자의 target 인자로 전달한 뒤 Thread 객체에서 start()를 호출한다.
class MyTask implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
Thread t1 = new Thread(new MyTask(), "thd0");
Thread t2 = new Thread(new MyTask(), "thd1");
t1.start();
t2.start();
Runnable 방식에서는 현재 작업을 실행 중인 Thread 객체가 따로 있으므로 Thread.currentThread()로 현재 스레드의 참조를 얻은 다음 getName()을 호출할 수 있다. 작업 클래스가 Thread를 상속하지 않아도 되므로 다른 클래스를 상속하면서 스레드 실행 작업을 정의할 수 있다는 장점이 있다.
| 구분 | Thread 상속 | Runnable 구현 |
|---|---|---|
| 작업 정의 | Thread 자식 클래스의 run() 재정의 | Runnable 구현 클래스의 run() 정의 |
| 객체 생성 | 자식 클래스 객체 생성 | Runnable 객체를 Thread 생성자에 전달 |
| 실행 | Thread 객체에서 start() 호출 | Runnable을 담은 Thread 객체에서 start() 호출 |
| 상속 활용 | 다른 클래스 상속에 제약이 생긴다. | 작업 클래스가 다른 클래스를 상속할 수 있다. |
제6장 스레드의 상태와 전이
1. 주요 실행 상태
생성된 스레드는 CPU를 얻어 실행되고 최종적으로 종료될 때까지 여러 상태를 거친다. 강의에서는 객체가 생성되었으나 아직 start()가 호출되지 않은 상태를 Startable, start()가 호출되었지만 아직 CPU를 얻기 전인 상태를 Runnable, CPU를 얻어 실제 명령을 수행 중인 상태를 Running으로 설명한다.
실행 도중 CPU를 잃거나 특정 조건을 기다리면 Not Running 상태로 이동한다. 이 범주에는 Blocked, Waiting, Timed_Waiting 상태가 포함된다. run()의 실행이 모두 끝나면 Dead 상태가 되어 다시 실행할 수 없다.
| 상태 | 의미 |
|---|---|
| Startable | 스레드 객체가 생성되었지만 start()가 아직 호출되지 않은 상태 |
| Runnable | start()가 호출되었으나 CPU를 획득하기 전인 실행 가능 상태 |
| Running | CPU를 얻어 실제 실행 중인 상태 |
| Not Running | Blocked, Waiting, Timed_Waiting 등 CPU를 사용하지 않는 상태 |
| Dead | run()의 실행이 종료된 상태 |
2. 상태 전이의 흐름
스레드 객체에서 start()를 호출하면 Startable에서 Runnable로 이동한다. CPU를 얻으면 Running이 되고, CPU를 다른 스레드에 양보하거나 빼앗기면 다시 Runnable로 돌아갈 수 있다. sleep(), join(), wait() 등으로 기다리면 Not Running 상태로 이동하며, 시간이 지나거나 조건이 충족되거나 깨움 신호를 받으면 다시 실행 가능한 상태로 돌아간다. run()이 끝나면 Dead 상태가 된다.
상태 흐름: 객체 생성 → start() → 실행 가능 → CPU 획득 → 실행 → 대기 또는 실행 가능 상태 반복 → run() 종료 → Dead 상태의 순서를 이해해야 한다.
제7장 Thread의 상태 제어 메소드
1. 우선순위 지정
setPriority(int newPriority)는 스레드의 우선순위를 설정한다. 높은 우선순위는 CPU를 얻을 가능성을 높이지만, 실행 순서를 완전히 고정한다는 의미는 아니다. 실제 스케줄링 결과는 실행 환경의 영향을 받는다.
2. sleep()과 yield()
Thread.sleep(long millis)는 현재 실행 중인 스레드를 지정된 시간 동안 멈추고 Not Running 상태로 이동시킨다. 다른 스레드가 해당 스레드의 interrupt()를 호출해 깨우면 InterruptedException이 발생한다.
Thread.yield()는 현재 실행 중인 스레드가 잠시 실행을 멈추고 Runnable 상태로 돌아가 CPU를 다른 스레드에 양보하도록 한다. 다만 양보 이후 어떤 스레드가 실제로 CPU를 얻는지는 스케줄러가 결정하므로 특정 스레드의 즉시 실행을 보장하지 않는다.
3. join()과 interrupt()
join()은 호출 대상 스레드가 종료될 때까지 현재 스레드가 기다리게 한다. 예를 들어 main 스레드에서 t1.join()을 호출하면 main 스레드는 t1이 끝날 때까지 진행을 멈춘다. join(long millis)는 최대 지정 시간 동안 기다린다. 기다리는 중 interrupt()를 받으면 InterruptedException이 발생한다.
interrupt()는 대상 스레드를 인터럽트한다. 스레드가 wait(), join(), sleep()에 의해 중단된 상태라면 그 상태에서 깨어나 실행 가능한 상태로 이동하고 InterruptedException을 통해 인터럽트 사실을 처리할 수 있다.
| 메소드 | 대상과 효과 | 예외 관련 |
|---|---|---|
sleep(millis) | 현재 스레드를 일정 시간 멈춘다. | InterruptedException |
yield() | 현재 스레드가 CPU를 양보하고 Runnable로 간다. | 보통 검사 예외 없음 |
join() | 대상 스레드가 끝날 때까지 현재 스레드가 기다린다. | InterruptedException |
interrupt() | 대상 스레드에 인터럽트를 전달한다. | 대기 중인 스레드가 예외로 깨어날 수 있다. |
제8장 Object의 대기와 알림 메소드
1. wait()
wait()는 현재 스레드가 어떤 객체를 처리하다가 대기 상태로 들어가게 하는 Object의 메소드이다. 다른 스레드가 같은 객체에 대해 notify() 또는 notifyAll()을 호출할 때까지 기다릴 수 있다. wait(long millis)는 지정된 시간 동안 대기하며, 시간이 지나면 깨어날 수 있다.
wait()는 해당 객체의 모니터를 확보한 synchronized 메소드나 synchronized 블록 안에서 호출해야 한다. wait()로 기다리는 동안 다른 스레드가 그 객체를 이용해 작업하고 알림을 보낼 수 있도록 동기화 잠금을 놓는다는 점이 중요하다.
2. notify()와 notifyAll()
notify()는 같은 객체에서 wait()로 대기 중인 스레드 가운데 하나를 깨운다. notifyAll()은 대기 중인 모든 스레드에 알린다. 이 메소드들도 객체의 모니터를 확보한 synchronized 영역 안에서 호출해야 한다.
Thread의 sleep(), yield(), join(), interrupt()는 스레드 실행 상태를 직접 제어하는 데 사용하고, Object의 wait(), notify(), notifyAll()은 공유 객체를 기준으로 스레드 사이의 대기와 통지를 조정하는 데 사용한다.
제9장 스레드 상태 제어 예제
1. yield()로 CPU 양보하기
두 스레드가 반복 출력하는 예에서 특정 이름의 스레드만 매 반복마다 Thread.yield()를 호출하게 할 수 있다. 이 스레드는 실행 기회를 자주 양보하므로 다른 스레드의 출력이 먼저 많이 나타날 가능성이 커진다. 그러나 yield()는 실행 순서나 양보받을 스레드를 보장하는 명령이 아니므로 결과는 매번 달라질 수 있다.
2. join()으로 main의 종료 순서 조정하기
main 스레드가 두 작업 스레드를 start()한 뒤 각각 join()하면 두 스레드가 모두 끝날 때까지 기다린다. 따라서 "main thread"와 같은 마지막 출력은 작업 스레드의 반복 출력이 끝난 뒤 나타난다. join()이 없다면 main의 출력이 작업 스레드 도중에 섞여 나타날 수 있다.
3. interrupt()로 잠든 스레드 깨우기
강의 예제에서는 세 스레드가 sleep()으로 긴 시간 잠든 뒤 interrupt()를 받으면 InterruptedException을 처리하면서 자신의 이름을 출력하고 다음 스레드를 다시 interrupt()한다. 첫 스레드를 깨우면 thd1, thd2, thd3 순으로 서로를 깨우는 흐름이 반복된다. main 스레드는 세 스레드에 join()하여 모두 종료된 뒤 마지막 메시지를 출력한다.
메소드 선택: 잠시 CPU를 양보하려면 yield(), 대상 스레드의 종료를 기다리려면 join(), sleep·wait·join 중인 스레드를 깨우려면 interrupt()의 역할을 구별한다.
제10장 스레드 간 간섭
1. 공유 객체에서 데이터 무결성이 깨지는 이유
여러 스레드가 하나의 공유 객체에 동시에 접근해 값을 읽고 수정하면 데이터 무결성이 깨질 수 있다. 예를 들어 공유 변수의 값을 1 증가시키는 연산은 하나의 완전히 분리할 수 없는 동작이 아니라 값을 읽고, 계산하고, 저장하는 여러 단계로 처리될 수 있다.
한 스레드가 기존 값 0을 읽은 뒤 아직 1을 저장하기 전에 다른 스레드도 0을 읽어 -1을 저장할 수 있다. 이후 첫 스레드가 자신이 계산한 1을 저장하면 두 연산이 의도한 순서대로 반영되지 않는다. 이처럼 실행 단계가 서로 끼어드는 현상이 스레드 간 간섭이다.
2. Counter 예제의 결과
하나의 Counter 객체에 대해 한 스레드는 100,000번 increment()를 호출하고 다른 스레드는 100,000번 decrement()를 호출한다고 하자. 이론적으로는 증가와 감소가 상쇄되어 0이 되어야 하지만, 메소드가 동기화되지 않았다면 간섭 때문에 실행할 때마다 0이 아닌 값이 나타날 수 있다.
join()은 두 작업이 끝날 때까지 main을 기다리게 할 뿐, increment()와 decrement()의 내부 연산을 상호 배제하지 않는다. 종료 대기와 공유 자원 동기화는 서로 다른 문제이다.
제11장 synchronized와 스레드 동기화
1. 동기화와 상호 배제
스레드 동기화는 서로 다른 스레드가 공유 자원을 다룰 때 데이터 무결성을 보장하도록 실행을 조정하는 것이다. 핵심 원리는 한 번에 오직 한 스레드만 해당 공유 객체의 보호된 코드 영역에 접근하도록 하는 상호 배제이다. 자바에서는 synchronized 키워드로 동기화 메소드나 동기화 블록을 만들 수 있다.
2. synchronized 메소드
인스턴스 메소드 선언에 synchronized를 붙이면 그 메소드를 실행하려는 스레드는 호출 대상 객체의 lock을 얻어야 한다. 한 스레드가 lock을 보유하고 synchronized 메소드를 실행하는 동안 다른 스레드는 같은 객체의 synchronized 메소드에 들어가지 못하고 기다린다. 실행이 끝나 lock이 해제되면 대기하던 다른 스레드가 lock을 얻어 실행할 수 있다.
class Counter {
private int c = 0;
public synchronized void increment() {
c++;
}
public synchronized void decrement() {
c--;
}
public int value() {
return c;
}
}
3. synchronized 블록
메소드 전체가 아니라 공유 자원을 수정하는 일부 코드만 보호할 수도 있다. synchronized(this) { c++; }처럼 동기화 대상 객체를 지정하고 중괄호 안에 임계 영역을 작성한다. 공유 자원으로 사용하는 객체가 lock의 기준이 된다.
public void increment() {
synchronized (this) {
c++;
}
}
| 방식 | 형식 | 보호 범위 |
|---|---|---|
| 동기화 메소드 | public synchronized void method() | 메소드 본문 전체 |
| 동기화 블록 | synchronized(객체) { ... } | 지정한 블록 내부 |
시험 핵심: synchronized는 특정 객체의 lock을 기준으로 상호 배제를 제공한다. 같은 공유 객체를 사용하는 스레드들이 동일한 lock으로 보호된 영역을 통과해야 간섭을 막을 수 있다.
핵심 개념 정리
스레드 생성과 실행
- 스레드는 하나의 프로세스 내부에서 실행되는 작은 실행 흐름이다.
- Thread를 상속하거나 Runnable을 구현해 run()에 작업을 정의할 수 있다.
- Thread 객체의 start()를 호출해야 새로운 실행 흐름에서 run()이 시작된다.
- 여러 스레드의 구체적인 실행 순서와 출력 순서는 예측할 수 없다.
상태와 제어
- 스레드는 생성, 실행 가능, 실행, 대기, 종료 상태 사이를 이동한다.
- sleep()은 일정 시간 중지, yield()는 CPU 양보, join()은 대상 스레드 종료 대기, interrupt()는 인터럽트 전달에 사용한다.
- wait()와 notify()/notifyAll()은 synchronized 영역에서 객체를 기준으로 대기와 알림을 조정한다.
간섭과 동기화
- 여러 스레드가 공유 자원의 읽기·수정·저장 과정에 동시에 개입하면 데이터 무결성이 깨질 수 있다.
- synchronized 메소드나 블록은 객체의 lock을 이용해 한 번에 한 스레드만 임계 영역에 접근하도록 한다.
- join()은 종료 순서를 조정하지만 공유 자원 내부 연산의 상호 배제를 대신하지 않는다.
최종 정리: 멀티 스레드 프로그램에서는 작업을 여러 실행 흐름으로 나누는 것만큼 실행 시점과 공유 자원 접근을 올바르게 제어하는 것이 중요하다. Thread와 Runnable로 작업을 시작하고, 상태 제어 메소드의 대상과 효과를 구별하며, 공유 데이터는 동일한 객체의 lock을 사용하는 synchronized 영역으로 보호해야 한다.
예상문제 20선
1. 스레드에 대한 설명으로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
스레드는 한 프로세스 안에서 독립적인 실행 순서를 가지는 작은 실행 흐름이며 경량 프로세스라고도 한다.
2. 멀티 스레드 프로그램의 실행 결과에 대한 설명으로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
각 스레드는 독립적으로 실행되며 CPU 배분은 스케줄러가 결정하므로 구체적인 실행 순서는 예측할 수 없다.
3. 현재 실행 중인 스레드의 참조를 반환하는 Thread의 정적 메소드는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
Thread.currentThread()는 그 코드를 현재 실행하고 있는 스레드의 참조를 반환한다.
4. 새 스레드의 실행 흐름을 시작해 run()이 실행되도록 하는 메소드는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
run()은 실행할 작업을 정의하고, start()가 새로운 스레드 실행을 시작해 run()을 호출되게 한다.
5. Runnable 구현 객체와 스레드 이름을 함께 지정하는 생성자는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
이 생성자는 Runnable의 run()을 실행 대상으로 지정하면서 새 Thread의 이름도 함께 설정한다.
6. Runnable 인터페이스를 이용한 스레드 생성 순서로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
Runnable은 실행 작업을 정의할 뿐 스레드 자체가 아니다. 이를 Thread의 target으로 전달한 뒤 Thread.start()를 호출해야 한다.
7. 강의에서 start()가 호출되었지만 아직 CPU를 얻기 전인 상태는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
start()가 호출된 스레드는 실행 가능한 Runnable 상태가 되며 CPU를 획득하면 Running 상태로 이동한다.
8. run() 메소드의 실행이 완전히 끝난 스레드의 상태는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
run()이 종료된 스레드는 Dead 상태가 되며 같은 Thread 객체를 다시 시작할 수 없다.
9. 현재 실행 중인 스레드를 지정된 시간 동안 멈추는 메소드는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
Thread.sleep(millis)는 현재 스레드를 지정 시간 동안 Not Running 상태로 보내며 InterruptedException을 처리해야 한다.
10. Thread.yield()의 기능으로 가장 적절한 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
yield()는 현재 스레드가 실행 기회를 양보하도록 하지만 다음에 어느 스레드가 실행될지는 보장하지 않는다.
11. main 스레드가 작업 스레드 t의 종료를 기다리게 하는 호출은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
t.join()을 호출한 현재 스레드는 t가 종료될 때까지 기다린다.
12. sleep(), wait(), join()에 의해 대기 중인 스레드를 깨울 수 있는 메소드는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
interrupt()를 받으면 대기 중인 스레드가 InterruptedException과 함께 깨어나 실행 가능한 상태로 이동할 수 있다.
13. Object의 wait()에 대한 설명으로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
wait()는 Object의 메소드이며 같은 객체의 모니터를 소유한 synchronized 메소드나 블록에서 호출해야 한다.
14. wait()로 대기 중인 스레드 하나를 깨우는 Object의 메소드는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
notify()는 같은 객체에서 wait() 중인 스레드 하나를 깨우며, notifyAll()은 모든 대기 스레드에 알린다.
15. 스레드 간 간섭이 발생하는 상황으로 가장 적절한 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
공유 값의 읽기·계산·저장 단계 사이에 다른 스레드가 개입하면 일부 갱신이 사라져 데이터 무결성이 깨질 수 있다.
16. 증가 스레드와 감소 스레드에 각각 join()만 사용했는데 최종 Counter 값이 0이 아닐 수 있는 이유는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
join()은 main이 작업 완료를 기다리게 하지만 increment()와 decrement()의 내부 단계가 동시에 실행되는 것을 막지 않는다.
17. 자바에서 스레드 동기화를 지정하는 키워드는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
synchronized는 메소드 또는 블록에 적용해 특정 객체의 lock을 기준으로 상호 배제를 제공한다.
18. 인스턴스 synchronized 메소드를 실행하기 위해 스레드가 얻어야 하는 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
인스턴스 synchronized 메소드는 호출 대상 객체의 lock을 획득한 스레드만 실행할 수 있다.
19. 메소드 전체가 아니라 일부 코드만 현재 객체를 기준으로 동기화하는 올바른 형식은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
synchronized(객체) 뒤의 블록이 해당 객체의 lock으로 보호되는 임계 영역이 된다.
20. synchronized의 핵심 목적으로 가장 적절한 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
synchronized는 상호 배제를 통해 공유 자원의 동시 수정에서 발생하는 스레드 간 간섭을 막고 데이터 무결성을 지킨다.
댓글
댓글 쓰기