Seven Concurrency - Thread & Lock
상호 배제와 메모리 모델
병렬성은 여러 일을 한꺼번에 실행하는 데 관한 것이다.
동시성은 여러 일을 한꺼번에 다루는 데 관한 것이다.
동시성 > 병렬성 : 동시성이 더 큰 개념인가??!!
동시성과 병렬성
- 멀티쓰레드가 각각 하나의 일을 처리해서 여러 일을 하는 것 : 병렬성
- 단일쓰레드가 여러 일을 하는 것 : 동시성
- 멀티쓰레드가 여러 X 여러 일을 하는 것 : 병렬성 + 동시성
사용할 수 있는 것은 synchronized
(에이싱크로나이즈드)와 volatile
(봘라타일) 뿐
1일차 정리
- 공유되는 변수에 대한 접근을 반드시 동기화(synchronized)한다.
- 쓰는 스레드와 읽는 스레드가 모두 동기화되어야 한다.
- 여러 개의 잠금장치를 미리 정해진 공통의 순서에 따라 요청한다.
- 잠금장치를 가진 상태에서 외부 메서드를 호출하지 않는다.
- 잠금장치는 최대한 짧게 보유한다.
내제된 잠금장치를 넘어서
Lock
, ReentrantLock
synchronized
비해 신규 추가된 Lock를 이용하면 잠금을 중간에 풀 수도 있고 잠금에 timeout도 지정할 수 있어서 더 낫다.
tryLock(timeout, timeunit)
과 같은 방식으로 즉 timeout을 지정해서 데드락를 빠져나가는 방식은 그다지 추천되지는 않는다.
애초에 timeout 대기 시간동안 다른 스레디 들이 대기 상태가 걸리는 것이고, 데드락을 완전히 회피할 순 없다.
또한 라이브락 상황에서 자유롭지 못하다. 다른 완화책들(timeout을 다르게 설정)이 있긴하지만
근본적으로는 데드락을 발생시키지 않게 하는 것 이 더 중요하다.
조건변수
어떤 일을 하기 위해서 조건을 기다리는 것
예를 들면 큐에 담긴 값을 제거하려고 하는 경우 적어도 큐가 하나의 값을 가질 때까지 기다리는 경우, 혹은 버터에 값을 넣을려고 할 때 공간이 생길 때까지 기다리는 경우
원자변수
AtomicInteger
자체적으로 동시성 이슈를 해결해줌. 말 그대로 메서드가 원자적임 또한 non-blocking(lock-free) 알고리즘 기반이기 때문에 데드락도 없음
volatile
이 키워드를 사용하면 해당 변수를 읽거나 쓰는 동작에 대해 명령어 순서가 뒤바뀌지 않는다.
그저 변수를 쓰거나 읽은 행위의 순서와 원자성만 보장할 뿐이다. 가능하면 volatile를 사용해서 직접 동시성을 제어하기 보다는 java.util.concurrent
를 활용하라
2일차 정리
- 잠금장치를 얻고자 기다리는 과정을 가로챌 수 있다.
- 잠금장치를 기다리는 동안 타임아웃이 발생할 수 있다.
- 잠금장치를 얻고 반납하는 동작이 임의의 순서로 일어날 수 있다.
- 임의의 조건이 참이 되는 것을 기다리기 위해 조건 변수를 사용할 수 있다.
- 원자 변수를 이용해서 잠금장치를 사용하는 것을 피할 수 있다.
2일차 자율학습
ReenterantLock
은 공정성(fairness)을 지원한다. 잠금장치가 “공정하다”는 것은 무엇을 믜미하는 것인가? 공정한 잠금장치를 이용해야하는 이유와 이용하지 않아야하는 이유는 무엇인가?- 공정성 옵션이 활성화 되면 잠금시간이 긴 것이 우선 실행된다. 그리고 공정하지 않는 잠금장치를 이용하면 성능이 더 나아진다.
ReenterantReadWriteLock
은 무엇인가? 그것은 ReentrantLock과 어떻게 다른가? 그것은 언제 사용하면 좋은가?- 읽기와 쓰기 별로 락을 분리한 것. 일반적으로 쓰기보다는 읽기가 많은 상황이 많은데 그럴 때 더 유리
- 불필요한 기상(spurious wakeup)이란 무엇인가? 그런 일은 언제 일어나며, 좋은 코드에서는 그런 일이 일어나지 않아야 하는 이유는 무엇인가?
- 조건 변수가 만족하지 않았어도 스레드는 깨어나서 조건이 만족되지 않음을 확인하고 다시 잠드는 것을 말하는 것
- 위와 같은 불필요한 기상이 잦으면 로직이 실행할 수 없음에도 지속적으로 CPU를 소모하게 되므로 성능에 좋지 않음. 그래서 발생하지 않도록 해야함
AtomicIntegerFieldUpdater
는 무엇인가? 그것은AtomicInteger
와 어떻게 다른가? 그것은 언제 사용하면 좋은가?- 리플렉션 기반으로 volatile 을 가지는 변수를 원자화 시키는 유틸리티
- 필드 직접 접근이 힘들 때 사용하면 좋을 것 같음
거인의 어깨 위에서
굳이 병행성 코드를 만들지 말고 java.util.concurrent
를 사용하자.
CopyOnWrite
항상 방어복사를 하는 것이 아니라 변경(Write)될 때만 복사를 해서 동시성 이슈 제거
완전한 프로그램
자료구조를 이용할 경우 직접 동기화 코드를 만들지 말고 java.util.concurrent
내 스레드에 안전한 자료구조들을 사용하자.
ConcurrentHashMap
를 사용할 경우 put
대신 원자적인 putIfAbsent
, replace
를 잘 이용하자.
3일차 정리
- 스레드를 직접 만드신 대신 스레드 풀을 이용한다.
CopyOnWriteArrayList
를 이용해 리스너 관리를 더 쉽고 효과적으로 만든다.- 생산자와 소비자가
ArrayBlockingQueue
를 이용해 더 효과적으로 의사소통을 한다. ConcurrentHashMap
을 이용해 맵에 대한 동시적인 접근을 지원한다.
3일차 자율학습
ForkJoinPool
에 대한 문서. fork/join 풀은 일반적인 스레드 풀과 어떻게 다른가? 어느 하나를 선호해야하는 이유나 상황은 무엇인가?- 작업을 가능한 잘게 쪼개서(재귀적으로) 가능한 병렬처리를 극대화 시키기 위함
- 작업 훔치기(work stealing)는 무엇이고 어떤 도움을 주는가?
java.util.concurrent
가 제공하는 도구를 이용해 작업 훔치기를 어떻게 구현하는가?- 프로세서의 작업이 종료되면 다른 프로세서의 대기열을보고 작업 항목을 “훔치기(도용)”합니다. 결과적으로, 작업 훔치기는 유휴 프로세서에 대한 스케줄링 작업을 분배하여 프로세서 사용 효율을 극대화 하는 것입니다.
ForkJoinPool
를 이용하면 구현.
CountDownLatch
와CyclicBarrier
는 어떤 차이가 있는가? 어느 하나를 선택해서 사용하는 이유나 상황은 무엇인가?- 암달의 법칙(Amdahl’s law)은 무엇인가? 우리의 단어 세기 알고리즘 성능이 개선될 수 있는 이론적인 한계에 대해서 이 법칙은 무엇을 이야기해주는가?
- https://namu.wiki/w/%EC%95%94%EB%8B%AC%EC%9D%98%20%EB%B2%95%EC%B9%99
- 병렬화가 불가능한 작업 때문에 성능 상승에 한계가 있다.