1. 동기화가 필요한 이유 (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) {
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();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("최종 카운트 값 (예상: 2000): " + counter.getCount());
}
}
2. synchronized 키워드로 해결하기
문제를 해결하려면 synchronized 키워드로 메서드를 동기화
더보기
class SynchronizedCounter {
private int count = 0;
// synchronized를 사용하여 동시 접근 방지
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
public class SynchronizedExample {
public static void main(String[] args) {
SynchronizedCounter counter = new SynchronizedCounter();
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();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("최종 카운트 값 (예상: 2000): " + counter.getCount());
}
}
3. synchronized 블록 사용하기
특정 코드 블록만 동기화하고 싶다면 synchronized 블록을 사용
class SynchronizedBlockCounter {
private int count = 0;
public void increment() {
synchronized (this) { // 특정 부분만 동기화
count++;
}
}
public int getCount() {
return count;
}
}
4. volatile 키워드
volatile은 변수 값이 캐시에 저장되지 않고 항상 메모리에서 읽도록 보장하는 키워드
class VolatileExample {
private volatile boolean running = true;
public void stop() {
running = false;
}
public void runTask() {
while (running) {
// running이 변경될 때까지 실행됨
}
System.out.println("스레드 종료");
}
}
volatile을 사용하면 여러 쓰레드가 값을 변경할 때 최신 값을 반영하도록 보장하지만, 동기화 기능은 없음
volatile은 간단한 플래그 값 변경에 적합하고, 복잡한 연산에는 synchronized를 사용해야 함
5. ReentrantLock (더 정교한 동기화)
synchronized 보다 더 유연한 동기화 기법
더보기
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class LockCounter {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock(); // 락 획득
try {
count++;
} finally {
lock.unlock(); // 락 해제
}
}
public int getCount() {
return count;
}
}
public class ReentrantLockExample {
public static void main(String[] args) {
LockCounter counter = new LockCounter();
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();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("최종 카운트 값 (예상: 2000): " + counter.getCount());
}
}
ReentrantLock은 synchronized보다 더 세밀한 제어 가능
예를 들어, 타임아웃 기능, 락을 여러 번 걸고 해제하는 기능 등이 필요할 때 사용
정리 : 언제 어떤 동기화 기법을 사용해야 할까?
| 동기화 방법 | 특징 | 언제 사용? |
| synchronized | 간단한 동기화, 코드가 직관적 | 일반적인 동기화 |
| synchronized 블록 | 특정 코드만 동기화 가능 | 일부 코드만 보호할 때 |
| volatile | 변수의 최신 값 보장 (동기화 X) | 단순한 플래그 값 변경 |
| ReentrantLock | 더 정교한 동기화 기능 제공 | 락을 세밀하게 관리할 때 |
'JAVA' 카테고리의 다른 글
| CompletableFuture (비동기 처리) (1) | 2025.03.12 |
|---|---|
| Executor 프레임워크 (0) | 2025.03.12 |
| 쓰레드(Thread) (0) | 2025.03.11 |
| Java Stream API 주요 메서드 정리 (2) | 2025.02.28 |
| 자바 기본 다지기 최종 (2) | 2025.02.27 |