스레드 순차 실행 보장 기법
다중 스레드 프로그래밍에서 특정 스레드들이 순서대로 실행되어야 하는 경우가 있습니다. 이러한 상황을 처리하는 세 가지 주요 방법을 살펴보겠습니다.
방법 1: join() 메서드 활용
join() 메서드는 특정 스레드의 실행이 완료될 때까지 현재 스레드를 차단합니다. 이를 통해 스레드가 순차적으로 실행되도록 보장할 수 있습니다.
public class TaskA implements Runnable {
@Override
public void run() {
System.out.println("A");
}
}
public class TaskB implements Runnable {
@Override
public void run() {
System.out.println("B");
}
}
public class TaskC implements Runnable {
@Override
public void run() {
System.out.println("C");
}
}
public class SequentialExecutionDemo {
public static void main(String[] args) throws InterruptedException {
Thread firstThread = new Thread(new TaskA());
firstThread.start();
firstThread.join();
Thread secondThread = new Thread(new TaskB());
secondThread.start();
secondThread.join();
Thread thirdThread = new Thread(new TaskC());
thirdThread.start();
}
}
위 방식은 직관적이지만, 스레드가 실제로 병렬로 실행되지 않으므로 성능상 단점이 있습니다.
방법 2: 단일 스레드 Executor 사용
ExecutorService를 사용하여 단일 스레드 풀을 생성하면 태스크가 제출된 순서대로 실행됩니다.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorSequentialDemo {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> System.out.println("Task A"));
executor.submit(() -> System.out.println("Task B"));
executor.submit(() -> System.out.println("Task C"));
executor.shutdown();
}
}
이 방법은 구현이 간단하고 효율적입니다. 모든 작업이 단일 스레드에서 순차적으로 처리됩니다.
방법 3: wait/notifyAll을 이용한 동기화
더 복잡한 상황이 필요한 경우, synchronized 블록과 wait(), notifyAll() 메서드를 사용하여 스레드 실행 순서를 제어할 수 있습니다.
public class SequenceController {
private volatile int currentStep = 1;
private static final int TOTAL_STEPS = 3;
public synchronized void executeFirst() throws InterruptedException {
while (currentStep != 1) {
wait();
}
System.out.println("First Task Execution");
currentStep = 2;
notifyAll();
}
public synchronized void executeSecond() throws InterruptedException {
while (currentStep != 2) {
wait();
}
System.out.println("Second Task Execution");
currentStep = 3;
notifyAll();
}
public synchronized void executeThird() throws InterruptedException {
while (currentStep != 3) {
wait();
}
System.out.println("Third Task Execution");
currentStep = 1;
notifyAll();
}
}
각 스레드는 특정 단계에서만 실행되며, 다른 스레드가 완료될 때까지 wait()를 통해 대기합니다.
public class FirstWorker implements Runnable {
private final SequenceController controller;
public FirstWorker(SequenceController controller) {
this.controller = controller;
}
@Override
public void run() {
try {
controller.executeFirst();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public class SecondWorker implements Runnable {
private final SequenceController controller;
public SecondWorker(SequenceController controller) {
this.controller = controller;
}
@Override
public void run() {
try {
controller.executeSecond();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public class ThirdWorker implements Runnable {
private final SequenceController controller;
public ThirdWorker(SequenceController controller) {
this.controller = controller;
}
@Override
public void run() {
try {
controller.executeThird();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public class SequenceDemoTest {
public static void main(String[] args) {
SequenceController controller = new SequenceController();
for (int i = 0; i < 5; i++) {
new Thread(new FirstWorker(controller)).start();
new Thread(new SecondWorker(controller)).start();
new Thread(new ThirdWorker(controller)).start();
}
}
}
원리 분석
join() 메서드의 내부 구현은 Object 클래스의 wait() 메서드를 호출합니다. 따라서 방법 1과 방법 3은 근본적으로 동일한 동기화 메커니즘을 사용합니다. 둘 다 스레드 간의 실행 순서를 보장하기 위해 스레드를 일시 중단하고 다시 시작하는 방식을 활용합니다.
실무에서는 상황에 맞는 적절한 방법을 선택해야 합니다. 간단한 순차 실행이 필요한 경우 단일 스레드 Executor를, 복잡한 동기화 시나리오가 필요한 경우 wait/notifyAll을 사용하는 것이 좋습니다.