동시성과 병렬성
- 동시성(Concurrency): 여러 작업을 동시에 처리하는 것처럼 보이도록 구현하는 방식으로, 단일 CPU에서 여러 작업이 빠르게 번갈아가며 실행됩니다. ex) 멀티스레드 프로그램.
- 병렬성(Parallelism): 여러 작업이 실제로 동시에 실행되는 방식으로, 다중 CPU나 다중 코어에서 각각 다른 작업을 동시에 수행합니다. ex) 멀티프로세싱.
멀티 스레드
멀티 스레드란 동시성을 달성할 수 있는 하나의 프로그래밍 기법이다.
Thread-Safe란?
다수의 스레드가 공유 자원에 접근해도 프로그램이 문제 없이 동작하는 것을 의미한다.
- Thread Safe 를 지키기 위한 방법은 4가지로 되어있다.
- Mutual exclusion (상호 배제)
- Atomic operation (원자 연산)
- Thread-local storage (쓰레드 지역 저장소)
- Re-entrancy (재진입성)
public class ThreadSafeExample {
private int count = 0;
// synchronized 키워드로 메서드에 락 설정
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
public static void main(String[] args) {
ThreadSafeExample example = new ThreadSafeExample();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
example.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("Final count: " + example.getCount());
}
}
increment와 getCount 메서드가 synchronized로 보호되어 여러 스레드가 동시에 접근해도 안전합니다.
- 가시성 문제: 한 스레드가 변경한 데이터가 다른 스레드에게 즉시 보이지 않을 때 발생합니다. CPU 캐싱이나 레지스터 최적화 때문에 발생하며, volatile 키워드 또는 동기화 메커니즘으로 해결할 수 있습니다.
- 원자성 문제: 작업이 중간에 끼어들기 없이 완전히 실행되거나 전혀 실행되지 않는 성질이 보장되지 않을 때 발생합니다. 예를 들어, i++ 같은 연산은 읽기, 증가, 쓰기 세 단계로 이루어져 원자적이지 않습니다.
자바의 동시성 이슈 해결 방법
- 동기화(synchronization):
- synchronized 키워드로 특정 코드 블록이나 메서드에 락을 걸어 한 번에 하나의 스레드만 접근 가능하게 함.
- volatile 키워드:
- 변수의 값을 메인 메모리에 즉시 반영하도록 하여 가시성 문제를 해결.
- Lock 클래스:
- java.util.concurrent.locks 패키지의 ReentrantLock 등을 사용해 세밀한 락 제어 가능.
- Atomic 클래스:
- java.util.concurrent.atomic 패키지에서 제공하는 클래스 (AtomicInteger, AtomicLong)로 원자적 연산 보장.
- ExecutorService:
- 스레드 관리를 위한 스레드 풀을 제공.
- Concurrent 컬렉션:
- ConcurrentHashMap, CopyOnWriteArrayList 등 Thread-Safe 자료구조.
5. volatile 키워드
- 정의: 변수의 값을 스레드별 캐시가 아닌 메인 메모리에 직접 저장하고 읽게 하여 가시성 문제를 해결.
- 제약:
- 원자성을 보장하지 않음. 예를 들어, count++는 volatile만으로 안전하지 않음.
- 복잡한 연산에는 적합하지 않음.
6. synchronized 키워드
- 정의: 코드 블록이나 메서드에 락을 걸어 한 번에 하나의 스레드만 해당 코드에 접근 가능하도록 만듦.
- 사용 예:
java복사편집public synchronized void increment() { count++; } public void decrement() { synchronized(this) { count--; } }
7. synchronized의 문제점
- 성능 저하:
- 락을 획득하고 해제하는 과정에서 비용이 발생.
- 불필요한 락 경쟁으로 인해 성능 문제가 생길 수 있음.
- 데드락(Deadlock):
- 두 스레드가 서로가 가진 락을 기다리며 무한 대기 상태에 빠질 가능성.
- 가시성 문제:
- 잘못된 동기화로 인해 여전히 가시성 문제가 발생할 수 있음.
8. synchronized 구현되어있는 방식
- 자바의 synchronized는 모니터 락(Monitor Lock) 개념을 사용하며, 객체의 내부 모니터(헤더)를 이용하여 락을 관리합니다.
- JVM 레벨에서 synchronized는 모니터(MonitorEnter/MonitorExit) 명령어로 컴파일되어 실행됩니다.
- 최적화를 위해 바이오닉 락(Biased Locking), 경량 락(Lightweight Locking), 중량 락(Heavyweight Locking) 같은 락 업그레이드/다운그레이드 메커니즘을 사용합니다.
9. atomic은 ???
- 원자적(atomic): 작업이 더 이상 나눠질 수 없는 단위로 실행되어, 작업 도중 다른 스레드가 개입할 수 없음을 의미합니다.
- 예: 하나의 작업(읽기, 쓰기 등)이 반드시 완료되거나 전혀 실행되지 않는 성질.
10. atomic 타입은??
- Atomic 타입: java.util.concurrent.atomic 패키지에 속한 클래스로, 원자적 연산을 제공하는 데이터 타입입니다.
- 주요 클래스:
- AtomicInteger
- AtomicLong
- AtomicReference
- AtomicBoolea
- 동시성과 병렬성은 실행 방식의 차이이며, 동시성은 논리적 병렬 처리, 병렬성은 물리적 병렬 처리입니다.
- Thread-Safe는 여러 스레드 접근에도 데이터가 안전함을 의미합니다.
- 가시성 문제는 메모리 일관성 문제이고, 원자성 문제는 작업의 중단 불가성을 보장하지 않는 경우를 의미합니다.
- 자바에서는 volatile, synchronized, Lock, ExecutorService, Atomic 클래스 등을 활용하여 동시성 문제를 해결합니다.