GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing github.com — 아이템 79. 과도한 동기화는 피하라 · Issue #192 · Study-2-Effective-Java/Effective-Java Open Labels 11장 동시성이펙티브 자바 11장 (동시성)이펙티브 자바 11장 (동시성) DescriptionDiscussed in https://github.com/orgs/Study-2-Effective-Java/discussions/191Originally posted by JoisFe April 2, 2023 아이템 79. 과도한 동기화는 피하라
과도한 동기화의 문제점
응답 불가와 안전 실패를 피하려면 동기화 메서드나 동기화 블록 안에서는 제어를 절대로 클라이언트에 양도하면 안됨!
public class ForwardingSet<E> implements Set<E> { private final Set<E> s; public ForwardingSet(Set<E> s) { this.s = s; } @Override public int size() { return s.size(); } @Override public boolean isEmpty() { return s.isEmpty(); } @Override public boolean contains(Object o) { return s.contains(o); } @Override public Iterator<E> iterator() { return s.iterator(); } @Override public Object[] toArray() { return s.toArray(); } @Override public <T> T[] toArray(T[] a) { return s.toArray(a); } @Override public boolean add(E e) { return s.add(e); } @Override public boolean remove(Object o) { return s.remove(o); } @Override public boolean containsAll(Collection<?> c) { return s.containsAll(c); } @Override public boolean addAll(Collection<? extends E> c) { return s.addAll(c); } @Override public boolean retainAll(Collection<?> c) { return s.retainAll(c); } @Override public boolean removeAll(Collection<?> c) { return s.removeAll(c); } @Override public void clear() { s.clear(); } @Override public boolean equals(Object o) { return s.equals(o); } @Override public int hashCode() { return s.hashCode(); } @Override public String toString() { return s.toString(); } } public class ObservableSet<E> extends ForwardingSet<E>{ public ObservableSet(Set<E> s) { super(s); } private final List<SetObserver<E>> observers = new ArrayList<>(); public void addObserver(SetObserver<E> observer) { synchronized (this.observers) { this.observers.add(observer); } } public boolean removeObserver(SetObserver<E> observer) { synchronized (this.observers) { return this.observers.remove(observer); } } public void notifyElementAdded(E element) { synchronized (this.observers) { for (SetObserver<E> observer : observers) { observer.added(this, element); } } } @Override public boolean add(E e) { boolean added = super.add(e); if (added) { notifyElementAdded(e); } return added; } @Override public boolean addAll(Collection<? extends E> c) { boolean result = false; for (E e : c) { result |= add(e); } return result; } }
@FunctionalInterface public interface SetObserver<E> { // ObservableSet에 원소가 더해지면 호출됨 void added(ObservableSet<E> set, E element); }
ObservableSet은 눈에 보기에 잘 동작할 것으로 보이나 문제가 있음public static void main(String[] args) { ObservableSet<Integer> set = new ObservableSet<>(new HashSet<>()); set.addObserver((s, e) -> System.out.println(e)); for (int i = 0; i < 100; ++i) { set.add(i); } }
set.addObserver(new SetObserver<>() { @Override public void added(ObservableSet<Integer> set, Integer element) { System.out.println(element); if (element == 23) { set.removeObserver(this); } } });
참고
다시 돌아와 ConcurrentModificationException을 던진 이유
set.addObserver(new SetObserver<>() { @Override public void added(ObservableSet<Integer> set, Integer element) { System.out.println(element); if (element == 23) { ExecutorService exec = Executors.newSingleThreadExecutor(); try { exec.submit(() -> set.removeObserver(this)).get(); } catch (ExecutionException | InterruptedException ex) { throw new AssertionError(ex); } finally { exec.shutdown(); } } } });
교착 상태에 빠지는 이유
위와 똑같은 상황이지만 불변식이 임시로 깨진 경우
해결 방법
public void notifyElementAdded(E element) { List<SetObserver<E>> snapshot = null; synchronized (this.observers) { snapshot = new ArrayList<>(this.observers); } for (SetObserver<E> observer : snapshot) { observer.added(this, element); } }
public class ObservableSet<E> extends ForwardingSet<E>{ public ObservableSet(Set<E> s) { super(s); } private final List<SetObserver<E>> observers = new CopyOnWriteArrayList<>(); public void addObserver(SetObserver<E> observer) { this.observers.add(observer); } public boolean removeObserver(SetObserver<E> observer) { return this.observers.remove(observer); } public void notifyElementAdded(E element) { for (SetObserver<E> observer : observers) { observer.added(this, element); } } ~~~
명시적으로 동기화한 곳이 사라졌다는 것이 중요한 포인트!
동기화 규칙기본 규칙은 동기화 영역에서는 가능한 한 일을 적게 하는 것!
과도한 동기화와 성능
가변 클래스를 작성하는 2가지 선택지
자바도 초장기에는 이 지침을 따르지 않은 클래스가 많았음
선택하기 어렵다면 동기화하지 말고 대신 문서에 "스레드 안전하지 않다" 라고 명시하자!클래스 내부에서 동기화 하는 경우 다양한 기법
여러 스레드가 호출할 가능성이 있는 메서드가 정적 필드를 수정한다면 그 필드를 사용하기 전에 반드시 동기화해야 함! (비결정적 행동도 용인하는 클래스라면 상관 없음)
정리
ActivityMetadataMetadataAssigneesLabels11장 동시성이펙티브 자바 11장 (동시성)이펙티브 자바 11장 (동시성) TypeProjectsMilestoneRelationshipsDevelopmentIssue actions |
| You are visitor 01402520 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |