동시 성능 기법
과거에는 HW가 좋아지면 application 성능도 좋아지는 "공짜 점심"을 누리던 시절이 있었다.
현재는 멀티코어 프로세서가 일반화 되어 있다.
JVM의 경우, JIT 컴파일 처럼 VM 스레드들(GC, JIT, 클래스 로딩, 모니터링 및 관리 등)이 멀티코어 환경에서 동시에 실행될 수 있다.
병렬성
암달의 법칙: T(N) = S + (1/N) * (T - s)
S: 순차 실행 파트
T: 총 태스크 소요 시간
N: 프로세서 개수
T(n): 프로세서 개수별 총 태스크 소요시간
프로세서 개수(N)을 무한히 증가시켜도, 총 소요시간은 순차 실행 시간보다 줄어들 수 없다.
만약 데이터 공유 없이 워크로드를 분산시킨다면 이론적으로 속도는 무한히 높일 수 있다.
하지만 어쩔 수 없이 일부 태스크를 순차처리 하게되거나 스레드 끼리 상태나 데이터를 공유한다면 통신 오버헤드가 발생한다.
java 동시성
만약 다른 스레드에서 같은 값을 변경한다면 원하는 결과가 나오지 않을 수 있다.
JMM
java에는 1.0 버전부터 공식적으로 JMM이란 메모리 모델이 존재했다
JMM은 다음과 같은 질문에 답을 찾는 모델이다
- 두 코어가 같은 데이터를 액세스 하면 어떻게 되는가?
- 언제 두 코어가 같은 데이터를 바라본다고 장담할 수 있는가?
- 메모리 캐시는 위 두 질문의 답에 어떤 영향을 미치는가?
JMM은 다음과 같은 내용을 약속한다
- 순서에 관한 보장
- 여러 스레드에 대한 업데이트 가시성 보장
JMM은 메모리의 일관성과 가시성을 어떻게 관리하느냐에 따라 두 가지 다른 방식을 고려한다.
1. 강한 메모리 모델: 전체 코어가 항상 같은 값을 바라본다
- 한 스레드에서 수행한 작업이 다른 모든 스레드에게 즉각적으로 반영되어야 한다
- 성능에 부담을 줄 수 있다
- 약한 메모리 모델: 코어마다 다른 값을 바라볼 수 있고, 그 시점을 제어하는 특별한 캐시 규칙이 있다
- 한 스레드의 변경 사항이 다른 스레드에 즉시 반영되지 않을 수 있다
- 특정 조건에서만 변경 사항을 다른 스레드에게 가시적으로 만들어주는 역할을 한다
➡️ JMM은 아주 약한 메모리 모델이다
약한 메모리 모델
- 약한 메모리 모델일수록 이식성이 좋다
- 만약 JVM이 강한 메모리 모델로 설계됐다면, native 수준에서 강한 메모리 모델을 지원하지 않는 HW를 위해 JVM에 별도 구현 작업이 필요하다.
- 약한 메모리 모델일수록 CPU 아키텍처 추세(멀티코어) 와 잘 어울린다.
- 각 코어가 독립적으로 작업을 수행할 수 있기 때문에, 추가 코어의 이점을 최대한 활용할 수 있기 때문이다
JMM의 application 보호법
- Happens-Before: 한 이벤트는 무조건 다른 이벤트보다 먼저 발생한다
- Synchronizes-With: 이벤트가 객체 뷰를 메인 메모리와 동기화시킨다
- As-If-Serial: 실행 스레드 밖에서는 명령어가 순차 실행되는 것 처럼 보인다
- Release-Before-Acquire: 한 스레드에 걸린 락을 다른 스레드가 그 락을 획득하기 전에 해제한다
Java에서의 thread
- Java에서 thread는 객체 상태 정보를 스스로 들고다닌다
- thread가 변경한 내용은 메인메모리로 곧장 반영된다
- 같은 데이터를 access하는 다른 thread가 다시 읽는다
동시성 라이브러리
Java의 동시성 라이브러리를 구성하는 핵심요소
- 락, 세마포어
- 아토믹스
- 블로킹 큐
- 래치
- 실행자
개발자가 해당 라이브러리의 구현원리를 알고있다면 동시성 라이브러리를 최적으로 활용할 수 있다.
CAS
- 예상되는 현재 값과 원하는 새 값, 메모리 위치(포인터)를 전달받아 두가지 일을 하는 아토믹 유닛이다
- 예상되는 현재 값을 메모리 위치에 있는 콘텐츠와 비교한다
- 두 값이 일치하면 현재 값을 원하는 새 값으로 교체한다
아토믹 유닛이란?
용어는 작업의 단위를 나타내며, 해당 작업은 중단되거나 나눌 수 없는 것으로 간주된다.
즉, 아토믹 연산은 완전히 수행되거나 전혀 수행되지 않는 성질을 가지고 있어서, 이러한 연산 중에는 어떠한 다른 연산도 끼어들 수 없다
최신 프로세서가 꽂힌 대부분의 하드웨어에 CAS 기능이 구현되어 있다.
CAS 하드웨어는 sun.misc.Unsafe 클래스를 통해 access 한다.
Unsafe
Unsafe는 내부 구현 클래스이다.
Unsafe는 표준 자바 플랫폼 API가 아니다.
이 클래스를 엄밀히 말해 핫스팟 VM에 직접 연결되고 깨질 우려가 높다. 하지만 Unsafe는 거의 모든 주요 프레임워크의 구현 핵심부를 차지하게 되었다.
Unsafe로 할 수 있는 일들은 다음과 같다
- 객체는 할당하지만 생성자는 실행하지 않는다
- 원메모리(raw memory)에 액세스하고 포인터 수준의 연산을 수행한다
- 프로세서별 하드웨어 특성(ex. CAS)을 이용한다
Unsafe로 할 수 있는 일을 해서 아래와 같이 고수준의 프레임워크 기능을 구현할 수 있다.
- 신속한 (역)직렬화
- thread-safe native memory access
- 아토믹 메로리 연산
- 효율적인 객체/메모리 레이아웃
- 커스텀 메모리 펜스
- 네이티브 코드와의 신속한 상호작용
- JNI에 관한 다중 운영체제 대체물
- 배열 원소에 volatile하게 access
Unsafe는 자바 SE 공식 표준은 아니지만, 워낙 업계에 활용도가 높아 사실상 표준이나 다름없다.