멀티스레딩은 Java의 강력한 기능 중 하나로 애플리케이션의 성능을 극대화하고 대규모 작업을 효율적으로 처리하는 데 중요한 역할을 합니다.
그러나 잘못된 동시성 제어로 인해 발생하는 문제는 디버깅이 어려운 경우가 많습니다.
이번 글에서는 Java 멀티스레딩의 핵심 개념과 실무에서 자주 사용되는 동시성 제어 방법, 그리고 문제 해결 방법을 실습 중심으로 다뤄보겠습니다.
1. 멀티스레딩 기본 개념
멀티스레딩은 여러 스레드가 동시에 작업을 수행하도록 하는 기법입니다. Java에서는 Thread
클래스 또는 Runnable
인터페이스를 사용해 스레드를 생성합니다.
예제: 간단한 스레드 생성
// 방법 1: Thread 클래스 확장
class MyThread extends Thread {
public void run() {
System.out.println("Thread is running: " + Thread.currentThread().getName());
}
}
// 방법 2: Runnable 인터페이스 구현
class MyRunnable implements Runnable {
public void run() {
System.out.println("Runnable is running: " + Thread.currentThread().getName());
}
}
// 실행
public class ThreadExample {
public static void main(String[] args) {
// Thread 클래스 사용
Thread thread1 = new MyThread();
thread1.start();
// Runnable 사용
Thread thread2 = new Thread(new MyRunnable());
thread2.start();
}
}
2. 동시성 문제: Race Condition
동시성 문제는 여러 스레드가 동일한 리소스를 접근하거나 수정하는 과정에서 발생할 수 있습니다. 그중 대표적인 문제는 Race Condition입니다.
예제: Race Condition 문제
class Counter {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class RaceConditionExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Final count: " + counter.getCount());
}
}
출력 결과: "Final count" 값이 2000이 아닐 수 있습니다. 이는 여러 스레드가 count
값을 동시에 수정하면서 Race Condition이 발생했기 때문입니다.
3. 동기화로 Race Condition 해결
Race Condition 문제를 해결하기 위해 동기화(Synchronization)를 사용합니다. Java에서는 synchronized
키워드를 사용하여 임계 구역(Critical Section)을 보호할 수 있습니다.
예제: synchronized 키워드
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
public class SynchronizedExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Final count: " + counter.getCount()); // 출력: 2000
}
}
4. Lock을 활용한 동기화
Java의 java.util.concurrent.locks
패키지에서 제공하는 Lock
인터페이스를 활용하면 더 세밀한 동기화 제어가 가능합니다.
예제: ReentrantLock 사용
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
public class LockExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Final count: " + counter.getCount()); // 출력: 2000
}
}
5. 실무에서 자주 사용하는 동시성 제어 도구
- Executors: 스레드 풀 관리를 위한 API.
- Atomic Variables:
AtomicInteger
,AtomicLong
등 동기화 없이 사용 가능한 변수. - CountDownLatch: 특정 조건에서 스레드 간 동기화.
- Semaphore: 동시 액세스 가능한 리소스 수를 제한.
정리
멀티스레딩은 복잡하지만 강력한 기능으로, 올바른 동시성 제어 기법을 사용하면 안전하고 효율적인 코드를 작성할 수 있습니다. 이 내용을 바탕으로 동시성 문제가 없는 안정적인 애플리케이션을 구현해 보세요!
'자바' 카테고리의 다른 글
[자바] JVM OutOfMemoryError 해결 가이드: 실무에서의 사례 분석 (1) | 2025.01.20 |
---|---|
[자바] Java에서 대규모 파일 데이터를 처리하는 효율적인 방법 (2) | 2025.01.20 |
[자바] Java Stream API를 활용한 데이터 처리 베스트 프랙티스 (1) | 2025.01.19 |
[자바] JSON 데이터 파싱후 JUnit5로 테스트 (json/application) (0) | 2024.02.18 |
[자바] Form 데이터 파싱후 JUnit5로 테스트 (x-www-form-urlencoded) (2) | 2024.02.17 |