고양이 여름이의 지식채널
JAVA 쓰레드(Thread) 프로그래밍 - 쓰레드 풀과 데드락 본문
자바 쓰레드 프로그래밍으로 쓰레드풀과 데드락에 대해서 알아봅니다.
쓰레드 풀(Thread Pool)
쓰레드 풀은 쓰레드를 효율적으로 사용하기 위한 방법입니다. 여러 개의 쓰레드를 미리 만들어 놓고, 작업이 필요할 때마다 쓰레드를 할당하여 작업을 수행합니다. 이를 통해 쓰레드를 반복적으로 생성하고 제거하는 오버헤드를 줄일 수 있습니다.
public class ThreadPool {
private final BlockingQueue<Runnable> taskQueue; // 작업 큐
private final List<PoolThread> threads; // 쓰레드 리스트
public ThreadPool(int numThreads) {
taskQueue = new LinkedBlockingQueue<>(); // 작업 큐 초기화
threads = new ArrayList<>(); // 쓰레드 리스트 초기화
for (int i = 0; i < numThreads; i++) {
threads.add(new PoolThread(taskQueue)); // 쓰레드 생성 및 작업 큐 할당
}
for (PoolThread thread : threads) {
thread.start(); // 쓰레드 시작
}
}
public void execute(Runnable task) throws InterruptedException {
taskQueue.put(task); // 작업 큐에 작업 추가
}
}
class PoolThread extends Thread {
private final BlockingQueue<Runnable> taskQueue; // 작업 큐
public PoolThread(BlockingQueue<Runnable> taskQueue) {
this.taskQueue = taskQueue; // 작업 큐 할당
}
public void run() {
while (true) {
try {
Runnable task = taskQueue.take(); // 작업 큐에서 작업을 가져옴
task.run(); // 작업을 실행함
} catch (InterruptedException e) {
e.printStackTrace(); // 예외 처리
}
}
}
}
쓰레드 풀(Thread Pool)을 구현한 것입니다. 작업을 큐(Queue)에 저장하고, 쓰레드들이 이를 처리하는 방식입니다. 각 쓰레드는 무한히 작업 큐에서 작업을 가져와서 처리합니다.
ThreadPool 클래스에서는 작업 큐와 쓰레드 리스트를 초기화하고, execute 메서드를 통해 작업을 큐에 추가합니다. PoolThread 클래스에서는 작업 큐를 할당받아서 무한히 작업을 가져와서 처리하는 로직을 구현합니다.
데드락(Deadlock)
데드락은 두 개 이상의 쓰레드가 서로 상대방이 가지고 있는 자원을 점유하고 기다리는 상황에서 무한히 대기하는 현상입니다. 이러한 상황이 발생하면 쓰레드는 정지되어 더 이상 실행을 계속할 수 없게 됩니다.
public class DeadlockExample {
private static final Object LOCK1 = new Object(); // LOCK1 객체
private static final Object LOCK2 = new Object(); // LOCK2 객체
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (LOCK1) { // LOCK1 객체를 락(lock)함
System.out.println("Thread 1: Holding LOCK 1...");
try {
Thread.sleep(10); // 일부러 쓰레드를 멈춤
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Waiting for LOCK 2...");
synchronized (LOCK2) { // LOCK2 객체를 락(lock)함
System.out.println("Thread 1: Holding LOCK 1 & 2...");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (LOCK2) { // LOCK2 객체를 락(lock)함
System.out.println("Thread 2: Holding LOCK 2...");
try {
Thread.sleep(10); // 일부러 쓰레드를 멈춤
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2: Waiting for LOCK 1...");
synchronized (LOCK1) { // LOCK1 객체를 락(lock)함
System.out.println("Thread 2: Holding LOCK 1 & 2...");
}
}
});
thread1.start(); // 쓰레드 1 시작
thread2.start(); // 쓰레드 2 시작
}
}
위 코드에서는 데드락(Deadlock) 상황을 만들기 위해 두 개의 객체(LOCK1과 LOCK2)를 사용합니다.
thread1은 LOCK1 객체를 락(lock)한 후 LOCK2 객체를 락(lock)하고,
thread2는 LOCK2 객체를 락(lock)한 후 LOCK1 객체를 락(lock)합니다.
이렇게 되면, thread1이 LOCK2 객체를 락(lock)한 상태에서 thread2가 LOCK1 객체를 락(lock)하려고 하고, thread2가 LOCK1 객체를 락(lock)한 상태에서 thread1이 LOCK2 객체를 락(lock)하려고 하면,
두 쓰레드 모두 대기 상태에 빠져서 무한정 기다리는 상황이 발생합니다. 이렇게 되면 데드락(Deadlock) 상황이 발생하게 되며, 프로그램이 멈추거나 더 이상 실행되지 않게 됩니다.
이상 쓰레드풀과 데드락에 대해서 알아보았습니다.
JAVA 쓰레드(Thread)와 동시성(Concurrency) 이해하기