공유 중인 가변 데이터는 동기화해 사용하라
synchronized
메서드나 블록을 한 스레드만 수행 할 수 있도록 하려면 synchronized 키워드를 사용하면 된다.
동기화 메서드나 블록에 들어간 스레드는 락의 보호하에 동작한다.
원자적
자바에서 long 과 double 을 제외한 변수(32bit)는 원자적이다.
즉, 동기화 없이 사용을해도 여러 스레드가 같은 변수를 수정하더라도 항상 정상적으로 값을 읽어오는게 보장된다.
하지만, 스레드가 필드를 읽을 때 항상 수정이 완전히 반영된 값을 얻는다 보장 하지만,
스레드가 저장한 값이 다른 스레드에 보이는가(visibillity)는 보장하지 않는다.
따라서, 원자적 데이터를 쓸때도 동기화 해야한다.
잘못된 코드
메인 스레드가 backgroundThread 를 실행시키고 sleep 후 stopRequested를 true로 바꾸면서
backgroundThread 가 종료될거라 생각하지만,
동기화가 되어있지 않기 때문에, 언제 종료될지 알수가없다.
backgroundThread 가 stopRequested 를 true 로 받지 못했다면, 영원히 종료 되지 않는다.
동기화 시켜서 처리를 해줘야한다.
volatile
voliatile 은 CPU 캐시를 사용하지 않기 때문에 메인 메모리로 바로 값을 쓰고 읽는다.
그래서 동기화가 되는 것.
하지만, volatile 은 읽고 연산 하고 쓰기 과정을 거치게 되는데
연산 하고 있는 중간에 다른 스레드가 들어와 값을 읽으면 변하지 않은 값을 읽어서
잘못된 결과를 초래할수있다.
- 안전실패라 한다
atomic 패키지
java.util.concurrent.atomic 패키지에는 락 없이도 thread-safe한 클래스를 제공한다.
동기화 보다 성능이 좋고, volatile 과 비교 했을때 atomic 은 원자성 까지 지원한다.
과도한 동기화는 피하라
과도한 동기화는 성능을 떨어뜨리고 , 교착상태에 빠뜨리게 된다.
동기화 영역을 최소한으로 줄이는 게 중요하다.
스레드보다는 실행자, 태스크, 스트림을 애용하라
스레드를 직접 만드는 것을 피하고
실행자 프레임 워크를 사용하자
java.util.concurrent 패키지에서 유연한 태스크 실행기능을 담은 실행자 프레임워크를 제공한다.
쉽게 작업 큐를 만들 수 있다.
스레드 풀의 종류?
Executors.newCachedThreadPool은 가벼운 프로그램을 실행하는 서버에 적합하다.
사용 가능 스레드가 없으면 즉시 새로운 스레드를 만들어서
사용량이 많다면 CPU 가 100% 로 가며 터질수있다.
따라서 사용량이 많은 서버에서는 Executors.newFixedThreadPool 를 사용해 스레드 갯수를 고정하자
wait와 notify보다는 동시성 유틸리티를 애용하라
concurrent 패키지에서는
크게 실행자 프레임워크, 동시성 컬렉션 그리고 동기화 장치를 제공한다.
동시성 컬렉션은
List, Queue, Map 같은 표준 컬렉션 인터페이스에 동시성을 추가한 것이다.
내부적으로 동기화를 수행한다
ex.) ConcurrentHashMap ..
synchronizedMap 보다 성능이 좋다
동기화 장치는
스레드가 다른 스레드를 기다릴 수 있게 하여 서로의 작업을 조율할 수 있도록 해준다.
대표적인 동기화 장치로는 CountDownLatch와 Semaphore가 있으며 CyclicBarrier와 Exchanger도 있다.
가장 강력한 동기화 장치로는 Phaser가 있다.
CountDownLatch는 하나 이상의 스레드가 또 다른 하나 이상의 스레드 작업이 끝날 때까지 기다린다.
생성자 인자로 받는 정수값은 래치의 countdown 메서드를 몇 번 호출해야 대기하고 있는 스레드들을 깨우는지 결정한다.
스레드 안전성 수준을 문서화하라
스레드 안전에도 수준이 나뉜다.
가장 높은 것 부터
불변(String, Long , BigInteger) >별도의 외부 동기화가 필요없는 무조건적인 스레드 세이프(Atomic..., ConcurrentHashMap) > 조건부 스레드 세이프(Collections.synchronized -> 일부 반환 값은 외부에서 동기화가 필요) >
스레드 안전하지 않음 이 있다.
조건부 스레드는 주의 하며 문서화를 해야한다.
지연 초기화는 신중히 사용하라
필요할때 까지 최대한 초기화를 늦추는 lazy initialize 는 최적화로 사용이 되지만,
하지만 지연 초기화가 오히려 성능을 낮출 수 있다.
프로그램의 동작을 스레드 스케줄러에 기대지 말라
운영체제는 여러 스레드가 실행 중이면 스레드 스케줄러가 스케줄링 하는데
이런 스케줄링 정책이 운영체제마다 다를 수 있다.
그렇기에 스케줄러에 의존하는 코드가 있다면 다른 플랫폼에 이식하기 어려워진다.
'Java > 이펙티브 자바' 카테고리의 다른 글
이펙티브 자바 - 12 장 직렬화 (0) | 2020.12.31 |
---|---|
이펙티브 자바 - 10장 예외 (0) | 2020.12.30 |
이펙티브 자바 - 9장 일반적인 프로그래밍 원칙 (0) | 2020.12.29 |
이펙티브 자바 - 8장 메서드 (0) | 2020.12.29 |
이펙티브 자바 - 7장 람다와 스트림 (0) | 2020.12.29 |