<이펙티브 자바> 11. 동기화
by BFine78. 공유 중인 가변 데이터는 동기화해서 사용해라
- Synchronized 키워드는 메서드나 블록을 한번에 한스레드씩 수행하도록 보장
- 동기화 없이는 스레드가 만든 변화를 다른 스레드가 확인 못할 수 있음
- Thread.stop 은 쓰지말자
얼마나 오래 실행될까
public class Stop{ static boolean stop; public static void main(){ Thread bg = new Thread(()->{ int i = 0; //if(!stop) while (!stop) i++; }) bg.start(); TimeUnit.SECONDS.sleep(1); // 내부적으로 Thread.sleep 사용 stopRequested = true; } }
아래 처럼 변경하면 정상동작 한다.
public class Stop{ static boolean stop; //static volatile boolean stop; private static synchronized void requestStop(){ stopRequested = true; } private static synchronized void stopRequested(){ return stopRequested; } public static void main(){ Thread bg = new Thread(()->{ int i = 0; while (!stopRequested()) i++; }) bg.start(); TimeUnit.SECONDS.sleep(1); // 내부적으로 Thread.sleep 사용 requestStop(); } }
가변데이터는 단일 스레드에서만 쓰도록하자
AtomicLong을 사용해보자
락이 없이 스레드에 안전한 프로그래밍을 지원한다.
Volatile은 두효과중 통신만 지원하지만 이 패키지는 원자성까지 지원한다.
getAndIncrement를 사용하면 같은효과를 낸다
Safety failure
volatile int next = 0; public static int genNext(){ return next++; }
증가 연산자는 필드에 두번 접근한다 -> 현재값을 읽고 그다음 1을 증가 시킨다
만약 중간에 다른스레드가 들어올경우 같은값을 반환할 여지가 생기면서 문제가 된다 -> synchronized 하자!
volatile 을 선언하면 동기화를 생략해도 된다.
❗️읽고 쓰기 모두 동기화 해야 올바르게 동작한다
배타적 수행
스레드간 통신
최적화 : 끌어올리기 기법 -> if(!stop)을 추가한다? -> 응답불가
메인스레드가 수정한 값을 백그라운드 스레드가 언제 보게될지 보증할 수 없다!!
78. 과도한 동기화는 피하자
성능을 떨어뜨리고 교착상태에 빠뜨린다.
응답불가와 안전실패를 피하려면 동기화 메서드나 동기화 블록 안에서는 제어를 절대로 클라이언트에 양도하면 안된다.
- 재정의 할수있는 메서드 호출 안됨
79. 스레드보다는 실행자, 태스크, 스트림을 쓰자
뛰어난 작업큐를 한줄로 생성할 수 있다.
- ExecutorService exec = Excutors.newSingleThreadExecutor();
- exec.excute(runnable) , exec.shutdown();
CashedThreadPool은 무거운 프로덕션 서버에는 좋지 못하다.
- 요청받은 태스트들이 큐에 쌓이지 않고 즉시 스레드에 위임
- 가용한 스레드가 없으면 새로 하나 생성
무거운 프로덕션 서버에서는 스레드개수를 고정한 Executors.newFixedThreadPool 이나 통제가능한 ThreadPoolExecutor를 사용하자
작업큐를 손수 만들거나 스레드를 직접다루는것은 삼가해야한다.
실행자 프레임워크에서는 작업단위와 실행 메커니즘이 분리된다.
작업단위를 나타내는 핵심 추상 개념이 태스크다.
- Runnable
- Callable(값을 반환하고 임의의 예외를 던질수 있다)
태스크를 실행하는 일반적인 메커니즘이 실행자 서비스 이다.
자바 7로 오면서 fork join 테스크를 지원하도록 확장
- 포크조인 풀이라는 특한한 실행자 서비스가 실행해준다
- 일을 잘게 분할해서 (하위 태스크로 나눔) 일이 끝난 스레드는 다른스레드의 남은 태스크를 가져와서 일을 처리할 수 도 있다.
- CPU를 최대한 활용하여 낮은 지연 및 높은 처리량을 달성할 수 있다.
81. wait과 notify 보다는 동시성 유틸리티를 사용하자
올바르게 사용하기 까다롭다.
util.concurrent의 고수준 유틸리티는 세 범주로 나눌 수 있다.
- 실행자 프레임워크, 동시성 컬렉션, 동기화 장치
- 동시성 컬렉션 : 표준 컬렉션(List, Map 등) 인터페이스에 동시성을 가미
- 동기화를 각자 내부에서 수행, 외부에서 락을 또쓰면 속도가 느려짐
ConcurrentHashMap은 동시성 및 속도도 무척 빠르다
synchronizedMap 보다 훨씬 성능이 좋다
내부를 보면 일부에만 syn가 걸려있다
putIfAbsent
for(AbstractHashedMap.HashEntry entry = this.data[this.hashIndex(hashCode,this.data.length)]; entry != null;entry = entry.next) { if (entry.hashCode == hashCode && this.isEqualKey(key, entry.key)) { return true; } } return false; } public boolean containsKey(Object key) { key = this.convertKey(key); int hashCode = this.hash(key);
BlockingQueue는 큐가 비어있으면 새로운 원소가 추가 될때까지 기다린다.
- 생상자- 소비자큐로 쓰기에 적합
자주 쓰이는 동기화 장치는 CountDownLatch와 Semaphore, Phaser(가장 강력한 동기화)
래치는 일회성 장벽으로 하나이상의 스레드가 또다른 하나이상의 스레드의 작업이 끝날때까지 기다리게한다.
- 생성자는 int값을 받고 래치의 countdown 메서드를 몇번호출해야 대기중인 스레드를 깨우는지결정한다.
시간간격을 잴때는 System.nanoTime을 사용하자
wait 메서드를 사용할때는 반드시 wait loop를 사용하자, 반복분 밖에서는 절대 호출하지 말자
일반적으로 notfiyAll을 사용하는게 합리적으로 안전한 조언이 될것이다.
82. 스레드 안전성 수준을 문서화하라
83. 지연초기화는 신중히 사용하라
필드의 초기화 시점을 그값이 처음 필요할때까지 늦추는 기법이다.
- 그값이 안쓰이면 초기화도 일어나지 않는다
주로 최적화 용도이고 클래스와 인스턴스 초기화때 발생하는 위험한 순환문제를 해결하는 효과도있다.
초기화 비용은 줄지만 지연초기화하는 필드에 접근하는 비용은 커진다.
성능때문에 정적필드를 지연초기화 해야한다면 지연초기화 홀더 클래스 관용구를 사용하자
static Class FieldHolder{ static FieldType field = computeFiledValue(); } private static FieldType getField(){return FieldHolder.field;}
정확성이나 성능이 스레드 스케줄러에 따라 달라지는 프로그램이면 다른 플랫폼에 이식하기 어렵다.
스레드는 당장 처리해야할 작업이 없다면 실행돼서는 안된다
- 공유객체의 상태가 바뀔때까지 쉬지않고 검사해서는 안된다는 뜻이다
Thread.yield는 테스트할 수단도 없다.
'개발서적 > 이펙티브 자바' 카테고리의 다른 글
<이펙티브 자바> 12. 직렬화 (0) | 2021.01.27 |
---|---|
<이펙티브 자바> 10. 예외 (0) | 2021.01.24 |
<이펙티브 자바> 8. 메서드 (0) | 2021.01.21 |
<이펙티브 자바> 7. 람다와 스트림 (0) | 2021.01.17 |
<이펙티브 자바> 6. 열거 타입과 애너테이션 (0) | 2021.01.15 |
블로그의 정보
57개월 BackEnd
BFine