item 79) 과도한 동기화는 피하라
과도한 동기화의 문제점
성능을 떨어뜨린다.
교착 상태에 빠뜨린다.
예측할 수 없는 동작이 발생할 수 있다.
동기화의 문제점
동기화 메서드나 블록 내에서는 제어를 클라이언트에 양도하면 안된다.
동기화된 영역 내에서는 재정의할 수 있는 메서드나, 클라이언트가 넘겨준 함수 객체를 호출하면 안된다.
해당 메서드가 어떤 일을 하는지 알지 못하며 통제할 수 없기 때문이다.
바깥 메서드들로 인해 예외를 일으키거나 교착상태에 빠지거나 데이터를 훼손할 수 있다.
ObservableSet
ForwardingSet을 상속받은 ObservableSet은 아래와 같다.
ObservableSet은 간단히 말하면, 내부적으로 들고 있는 Set에 원소가 추가되면 등록되어 있던 Observer들에게 notify해주는 클래스이다.
addObserver, removeObserver의 인자로는 아래 콜백 인터페이스의 인스턴스를 건넨다.
재정의된 외부 메서드 사용 시 에러 발생
addObserver를 통해 아래와 같이 직접 클라이언트가 작성한 함수 객체를 넘겨준다.
클라이언트가 직접 정의한 added 메서드 호출이 notifyElementAdded가 Observers의 리스트를 순회하는 도중에 발생하기 때문에, removeObserver가 수행되면서 ConcurrentModificationException이 발생한다.
로직의 흐름 간단 정리
main에서 set.addObserver() 호출 -> ObservableSet의 List<SetObserver>에 SetObserver 추가
for loop에서 ObservableSet의 add() 호출 -> notifyElementAdded() 호출
notifyElementAdded()에서 List<SetObserver>를 순회하며, 클라이언트가 main에서 정의한 added()를 호출
added 메서드에서 조건이 만족되면 removeObserver() 를 호출
removeObserver()에서는 자신을 수정하는 것을 막지 못해 원소가 제거된다.
notifyElementAdded()에서 순회 중인 동기화 블록에서 ConcurrentModificationException이 발생한다.
재정의된 외부 메서드 사용 시 교착 상태 발생
동기화된 영역 내에서 외부 메서드를 호출하면 교착 상태에 빠질 수 있다.
아래 예제는 직접 removeObserver를 호출하지 않고, 실행자 서비스(ExecutorService)를 사용해 다른 스레드에게 부탁하는 방식을 사용한다.
실행자 서비스의 스레드는 락을 얻을 수 없어 removeObserver를 못하고, 메인 스레드는 백그라운드 스레드가 Observer를 제거하기를 기다리기 때문에 교착상태가 발생한다.
재정의된 외부 메서드 문제 해결 방법
동기화 블록 밖에서 외부 메서드를 호출한다.
동기화 블록 밖에서 외부 메서드를 호출하는 것을 열린 호출이라고 부르며, 실패 방지 효과와 동시성 효율 개선의 효과가 있다.
CopyOnWriteArrayList를 사용한다.
항상 깨끗한 복사본을 만들어 수행하도록 구현해 락이 필요없으며 빠르다.
이처럼 동기화 영역에서는 작업을 최소화해야 한다.
Lock을 얻고, 공유 데이터를 검사하고, 필요하면 수정하고, Lock을 놓아야 한다.
동기화와 성능
병렬로 실행할 기회를 잃고, 모든 코어가 메모리를 일관되게 보기 위한 지연시간이 동기화의 진짜 비용이다.
따라서 과도한 동기화를 피해야 한다.
가변 클래스 작성 시 아래의 두 개의 선택지 중 하나를 따라야 한다.
동기화를 전혀 고려하지 말고, 사용하는 클래스가 외부에서 알아서 동기화하게 한다.
동기화를 내부에서 수행해 thread-safe한 클래스로 만든다. (클라이언트가 외부에서 객체 전체에 락을 거는 것보다 효율적일 경우)
Java의 라이브러리 중 java.util은 첫 번째 방법을, java.util.concurrent는 두 번째 방법을 택했다.
선택하기 어렵다면 동기화하지 말고 "스레드 안전하지 않다."라고 명시한다.
동시성을 높이는 기법
락 분할, 락 스트라이핑, 비차단 동시성 제어 등 다양한 기법을 사용할 수 있다.
락 분할(Lock Splitting)
하나의 클래스에서 기능적으로 Lock을 분리해서 사용하는 것(읽기 전용 Lock, 쓰기 전용 Lock)
락 스트라이핑(Lock Striping)
자료구조 관점에서 한 자료구조 전체가 아닌 일부분에 락을 적용하는 것
비차단 동시성 제어(NonBlocking Concurrency Control)
Last updated