싱글스레드 프로세스의 경우 프로세스 내에서 단 하나의 스레드만 작업하기 때문에 별 문제가 없지만, 멀티스레드 프로세스의 경우 여러 스레드가 같은 프로세스 내의 자원을 공유해서 작업하기 때문에 서로 작업에 영향을 준다.
한 스레드가 진행중인 작업을 다른 스레드가 간섭하기 못하도록 막는 것을 스레드 동기화라고 한다. 멀티 스레드일 경우 두 개 이상의 스레드가 공유 데이터에 동시에 접근하게 되면 예상과 벗어난 결과가 나타날 수 있다.
Synchronized
synchronized 키워드를 사용하여 임계영역을 설정
메소드 전체를 지정하는 것 보다 특정 블럭을 지정하는 것이 성능에 좋다.
싱글스레드일때는 괜찮으나 멀티스레드 일 경우 성능에 문제가 생길 수 있다.
public void execute(String topic, Object payload) {
makeThread();
synchronized (queue) {
try {
queue.addLast(new TopicData(topic, payload));
} catch (Exception e) {
// Do nothing
}
queue.notifyAll();
}
}
Volatile(볼러타일)
Concurrent 패키지 사용
concurrentHashMap
버킷 단위로 동기화를 처리. 이전 버전의 세그먼트 분할 방식이나 synchronized 처리와 비교해 최소한의 락 사용으로 스레드 안정성을 유지한다. (전체 Map에 대하여 락을 거는 것이 아니라, 필요한 부분만 table bucket에 대한 락을 사용하여 성능을 높임.)
synchronized 처럼 스레드의 접근을 제한하는 것이 아니라 volatile과 cas 연산을 활용해서 스레드를 동기화한다.
public static void main(String[] args) {
Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
concurrentHashMap.put("data1", 1);
concurrentHashMap.put("data2", 2);
concurrentHashMap.put("data3", 3);
}
copyOnWriteArrayList
set,add 과정에서 락과 함께 array를 copy후 write하는 방식으로 처리하기 때문에 get을 수행할 때 thread-safe하게 조회 가능하다.
동기화된 arrayList를 생성할 수 있음.(synchronizedList보다 성능이 좋다)
read 작업량 < write 작업량 : SynchronizedList
read 작업량 > write 작업량 : CopyOnWriteList
번외) 컨슈머에서 가져온 데이터들을 summary하여 쓸 때 SynchronizedList를 사용하여 처리하였음.
private List<Report> reportList = Collections.synchronizedList(new ArrayList<>());
public void appendData (Data record, boolean retry){
Report report
= Report.builder()
.yyyymmdd(record.getYyyymmdd())
...
.build();
if(retry){
reportRetryList.add(report);
} else {
reportList.add(report);
}
}
```
Atomic
** CAS(Compare and Swap)
- 멀티스레드 환경에서 동시성 처리를 위한 방법 중 하나로, 특정 메모리 위치 값이 주어진 값과 동일한 경우 해당 메모리 주소를 새로운 값으로 교체한다. Atomic 연산으로 동시성 문제를 처리하는 Non-blocking 방식이다.
멀티스레드는 공부할 때마다 헷갈리고 어렵지만,꾸준히 하다 보면 하나씩 이해하는게 재밌는 파트이다. 아직 부족한게 많지만 조금씩 공부하면서 정리해보겠다.