조슈아 블로크 님의 "Effective Java" 책을 정리한 포스팅 입니다.
1. wait와 notify보다는 동시성 유틸리티를 애용하라
- 사용하기기 아주 까다로움으로 고수준 동시성 유틸리티를 사용하자
실행자 프레임워크
- 스레드 풀 관리와 작업 실행을 위한 구현 제공
- 스레드 생성과 관리를 추상화하여 직접 스레드 작업을 제어하지 않아도 됨
동시성 컬렉션
- 동시 접근을 처리하기 위해 내부적으로 락 매커니즘을 활용
- 여러 메서드를 원자적으로 묶어 호출하는 일 불가
ConcurrentHashMap
BlockingQueue
- 생산자-소비자 패턴에 적합
- 큐가 비어 있을 떄 소비가자 대기하거나, 큐가 꽉 찼을 떄 생산자가 대기하는 방식으로 동작
동기화 장치
- 멀티스레드 작업에서 스레드 간 협력을 지원하기 위한 유틸리티
CountDownLatch
- 스레드가 특정 조건이 만족될 떄까지 기다리게 하는 도구
- await(): 카운트가 0이 될 때까지 대기
2. 지연 초기화는 신중히 사용하라
지연 초기화
- 필드의 초기화 시점을 그 값이 처음 필요할 때까지 늦추는 기법
- 필드를 사용하는 인스턴스의 비율이 낮은 반면, 그 필드를 초기화하는 비용이 클 경우
장점
- 인스턴스 생성 시의 초기화 비용은 줄어듬
단점
- 필드에 접근하는 비용은 커짐
멀티스레드 환경
synchronized 접근자
public class LazyInitExample {
private Object field;
public synchronized Object getField() {
if (field == null) {
field = new Object(); // 초기화
}
return field;
}
}
- 안전하고 직관적
- 동기화 오버헤드 발생
지연 초기화 홀더 클래스
public class LazyInitExample {
private static class Holder {
private static final Object INSTANCE = new Object(); // 초기화
}
public static Object getInstance() {
return Holder.INSTANCE;
}
}
- 성능 좋음
- 스레드 안전
- 클래스 로딩 시점의 스레드 안전성 활용
이중검사 관용구 사용
public class LazyInitExample {
private volatile Object field;
public Object getField() {
if (field == null) { // 첫 번째 검사 (동기화 없음)
synchronized (this) {
if (field == null) { // 두 번째 검사 (동기화)
field = new Object(); // 초기화
}
}
}
return field;
}
}
- 초기화가 여러번 수행되지 않도록 두번 검사 수행
3. 프로그램의 동작을 스레드 스케줄러에 기대지 말라
의존하면 안되는 이유
동작의 비결정성
- 스레드 스케줄러는 다양한 요인에 의존 (OS, JVM, CPU 등)
이식성 문제
- 정확성이나 성능이 스레드 스케줄러에 따라 달라지는 프로그램일 경우, 다른 플랫폼에 이식하기 어려움
디버깅 어려움
성능 측정 어려움
의존하면 안되는 속성
Thread.yield
- 현재 실행 중인 스레드가 자발적으로 CPU를 포기하고, 실행 준비 상태로 돌아가도록 요청하는 메서드
- JVM과 OS 별로 동작되거나 안될 수 있음
스레드 우선순위
- 스레드에 우선순위를 부여하는 힌트로 제공됨
- JVM과 OS 별로 처리 방식이 다름
'Java' 카테고리의 다른 글
[Effective Java] 12-2. 직렬화 (1) | 2025.01.03 |
---|---|
[Effective Java] 12-1. 직렬화 (0) | 2025.01.03 |
[Effective Java] 11-1. 동기화 (0) | 2025.01.01 |
[Effective Java] 10-2. 예외 (0) | 2025.01.01 |
[Effective Java] 10-1. 예외 (0) | 2025.01.01 |