반응형
- Synchronization: Use the synchronized keyword to ensure that only one thread can access a critical section of code at a time. This helps prevent race conditions where multiple threads might try to modify shared data simultaneously.
- Locks: Java provides various lock implementations like ReentrantLock, ReadWriteLock, and StampedLock which offer more flexibility and functionality compared to intrinsic locks (synchronized). They llow finer control over locking mechanisms and can help avoid deadlock situations.
- Thread-safe data structures: Instead of using standard collections like ArrayList or HashMap, consider using their thread-safe counterparts from the java.util.concurrent package, such as ConcurrentHashMap, CopyOnWriteArrayList, and ConcurrentLinkedQueue. These data structures are designed to be safely accessed by multiple threads without external synchronization.
- Atomic variables: Java's java.util.concurrent.atomic package provides atomic variables like AtomicInteger, AtomicLong, etc., which ensure that read-modify-write operations are performed atomically without the need for explicit locking.
- Immutable objects: Immutable objects are inherently thread-safe because their state cannot be modified once created. If an object's state needs to be shared among multiple threads, consider making it immutable.
멀티 스레드 환경에서 안전하게 코딩하는 기본적인 다섯가지 방법에 대해 살펴본다. 자바 기준
1. synchronized 키워드 사용(직접적인 락은 아니지만 락과 같은 것; monitor lock)
- 함수 자체 그리고 함수 안에서도 블락으로 지정하여 사용 가능
- allowing only one thread to execute at any given time.
- The lock behind the synchronized methods and blocks is a reentrant. This means the current thread can acquire the same synchronized lock over and over again while holding it(https://www.baeldung.com/java-synchronized)
2. 명시적으로 lock interface를 구현한 구현체를 사용
lock()/tryLock() 함수로 락을 걸고 unlock()함수로 반드시 락을 해제해야한다.(아니면 데드락..)
Condition 클래스를 통해 락에 대한 상세한 조절이 가능
- ReentrantLock implements Lock
- synchronized와 같은 방법으로 동시성과 메모리를 핸들링하지만 더 섬세한 사용이 가능하다.
- ReentrantReadWriteLock implements ReadWriteLock
- Read Lock – 쓰기락이 없거나 쓰기락을 요청하는 스레드가 없다면, 멀티 스레드가 락 소유 가능
- Write Lock – 쓰기/읽기 락 모두가 없는 경우 반드시 하나의 스레드만 락을 소유한다.
- StampedLock(자바8 도입, 읽기/쓰기 락 모두 제공)
- 락을 걸면 long 타입의 stamp를 반환함 해당 값으로 락을 해제하거나 확인 가능
- optimistic locking임(수정사항이 별로 없다는 가정하에 읽기에 개방적)
- https://www.baeldung.com/java-concurrent-locks
3. thread safe 한 자료구조 사용
- java.util.concurrent 패키지 참고(from java 5)
- ConcurrentHashMap, ConcurentLinkedQueue, ConcurrentLinkedDeque 등
- ConcurrentHashMap: This class is a thread-safe implementation of the Map interface. It allows multiple threads to read and modify the map concurrently without blocking each other. It achieves this by dividing the map into segments, each of which is independently locked.
- CopyOnWriteArrayList: This class is a thread-safe variant of ArrayList. It creates a new copy of the underlying array every time it is modified, ensuring that iterators won't throw ConcurrentModificationException. This makes it suitable for scenarios where reads are far more frequent than writes.
- ConcurrentLinkedQueue: This class is a thread-safe implementation of the Queue interface. It is designed for use in concurrent environments where multiple threads may concurrently add or remove elements from the queue. It uses non-blocking algorithms to ensure thread safety.
- BlockingQueue: This is an interface that represents a thread-safe queue with blocking operations. Implementations like LinkedBlockingQueue and ArrayBlockingQueue provide blocking methods like put() and take() which wait until the queue is non-empty or non-full before proceeding.
- ConcurrentSkipListMap and ConcurrentSkipListSet: These classes provide thread-safe implementations of sorted maps and sets, respectively. They are based on skip-list data structures and support concurrent access and updates.
4. thread safe 한 변수 사용
- java.util.concurrent.atomic 패키지 참고(from java 5)
- AtomicInteger, AtomocLong, AtomicBoolean 등
- 스레드 끼리 동기화 필요한 변수에 사용(상태 공유 등); 관련 성능 향상 시
- 락으로 인한 오버헤드나 컨테스트 스위칭 비용 감소
- non blocking 상황에서(스레드 별 독립적인 작업 시)
5. immutable object 불변 객체 사용
- String, Integer, Long, Double 등 과 같은 wrapper 클래스 사용
- private final로 선언
- 생성자 이용하여 initialize
- setter 함수나 수정가능 함수 제공하지 않기
6. volatile
- 변수를 Main Memory에 저장하겠다고 명시하는 것
- 각 스레드는 메인 메모리로 부터 값을 복사해 CPU 캐시에 저장하여 작업하는데 volatile은 CPU 캐시 사용 막고 메모리에 접근해서 실제 값을 읽어오도록 설정하여 데이터 불일치를 막음
- 자원의 가시성: 메인 메모리에 저장된 실제 자원의 값을 볼 수 있는 것
- 멀티쓰레드 환경에서 하나의 쓰레드만 read&write하고 나머지 쓰레드가 read하는 상황에 사용
- Read : CPU cache에 저장된 값 X , 메인 메모리에서 읽음
- Write : 메인 메모리에 작성
- CPU Cache보다 메인 메모리가 비용이 더 큼(성능 주의)
- 가장 최신 값 보장
what to choose
synchronized
- 여러 쓰레드가 write하는 상황에 적합
- 가시성 문제해결 : synchronized블락 진입 전/후에 메인 메모리와 CPU 캐시 메모리의 값을 동기화 하여 문제 없도록 처리
volatile
- 하나의 쓰레드만 read&write하고 나머지 쓰레드가 read하는 상황에 적합
- 가시성 문제해결 : CPU 캐시 사용 막음 → 메모리에 접근해서 실제 값을 읽어오게 함
- Read : CPU cache에 저장된 값 X , 메인 메모리에서 읽음
- Write : 메인 메모리에 작성
AtomicIntger
- 여러 쓰레드가 read&write를 병행
- 가시성 문제해결: CAS알고리즘
- 현재 쓰레드에 저장된 값과 메인 메모리에 저장된 값을 비교하여
- 일치할 경우 새로운 값으로 교체
- 불일치할 경우 실패하고 재시도
- 현재 쓰레드에 저장된 값과 메인 메모리에 저장된 값을 비교하여
728x90
반응형
'개발 > java' 카테고리의 다른 글
[java] try with resources; close (1) | 2024.05.09 |
---|---|
[java] 객체 소팅 시 비교하는 법 comparable, comparator (0) | 2024.05.08 |
[mysql] LocalDateTime.toLocalDate().atTime(LocalTime.MAX) 시간 반올림 이슈 (1) | 2024.02.16 |
[java] LocalDateTime, ZonedDateTime, OffsetDateTime (0) | 2024.02.14 |
[java] stream.concat, stream.generate (0) | 2024.02.06 |