JAVA

[JAVA] 스레드 안전(Thread-safe) 완벽 가이드 : 동기화, 원자적 연산, 락을 통한 동기화

yebin0322 2025. 2. 10. 16:57
반응형

Thread-safe(스레드 안전)

  • 여러 개의 스레드가 동시에 접근해도 문제가 발생하지 않도록 설계된 것

필요 이유

  • 멀티스레드 환경에서는 여러 개의 스레드가 동시에 같은 데이터를 수정하려고 하면 Race Condition(경쟁 상태)가 발생할 수 있기 때문
  • 예를 들어, 하나의 변수를 여러 스레드가 동시에 변경하려고 하면 의도하지 않은 값이 나올 수 있는 것

만드는 법

  1. synchronized 키워드 사용
  • increment() 메소드를 한 번에 하나의 스레드만 실행할 수 있도록 synchronized를 추가하면 됨
  • class Counter { private int count = 0; public synchronized void increment() { // 동기화 count++; } public int getCount() { return count; } }
  • synchronized를 사용하면 한 번에 하나의 스레드만 increment()를 실행할 수 있음
  1. AtomicInteger 사용
  • 내부적으로 Thread-safe하게 동작하도록 설계되어 있기 때문에 동기화 없이도 안전하게 값을 증가할 수 있음
  • AtomicInteger는 CAS(Compare-And-Swap) 기법을 사용하여 동기화 없이도 안전하게 동작
import java.util.concurrent.atomic.AtomicInteger;


class Counter {  
private AtomicInteger count = new AtomicInteger(0);

public void increment() {
    count.incrementAndGet(); // 원자적으로 증가
}

public int getCount() {
    return count.get();
}

}
  1. Lock 사용(ReentrantLock)
  • 더 정교한 스레드 동기화 제어 가능
  • 반드시 lock.lock() 후 finally에서 lock.unlock()을 호출해야 함
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;
}


}
  1. Thread-safe 컬렉션 사용
  • Java에서는 ArrayList 같은 일반 컬렉션 클래스는 Thread-safe하지 않음
  • 멀티스레드 환경에서 안전한 컬렉션을 사용하려면 ConcurrentHashMap이나 CopyOnWriteArrayList 같은 Thread-safe 컬렉션을 사용해야 함
import java.util.concurrent.CopyOnWriteArrayList;

public class ThreadSafeListExample {  
public static void main(String\[\] args) {  
CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();
Runnable task = () -> {
        for (int i = 0; i < 100; i++) {
            list.add(i);
        }
    };

    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("List size: " + list.size()); // 항상 정확한 값 출력
}
}
방법 설명
synchronized 간단하지만 성능 저하 가능성 있음
AtomicInteger 원자적 연산을 제공하여 성능이 더 좋음
ReentrantLock 동기화보다 유연한 제어 가능 (락 획득/해제)
Concurrent Collections 멀티스레드 환경에서 안전한 컬렉션
반응형