// 78-1 잘못된 코드 - 이 프로그램은 얼마나 오래 실행될까?
private static boolean stopRequested;
public static void main(String[] args) throws InterruptedException{
Thread backgroundThread = new Thread(() -> {
int i = 0;
while (!stopRequested)
i++;
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
// 78-2 적절히 동기화해 스레드가 정상 종료한다.
public class StopTread{
private static boolean stopRequested;
**private static synchronized void requestStop() {
stopRequested = true;
}
private static synchronized boolean stopRequested() {
return stopRequested;
}**
public static void main(String[] args) throws InterruptedException{
Thread backgroundThread = new Thread(() -> {
int i = 0;
while (**!stopRequested()**)
i++;
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
**requestStop();**
}
}
// 78-3 volatile 필드를 사용해 스레드가 정상 종료한다.
public class StopThread {
private static **volatile** boolean stopRequested;
public static void main(String[] args) throws InterruptedException{
Thread backgroundThread = new Thread(() -> {
int i = 0;
while (!stopRequested)
i++;
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}
// 78-4 잘못된 코드 - 동기화가 필요하다.
private static volatile int nextSerialNumber = 0;
public static int generateSerialNumber() {
return nextSerialNumber++;
}
// 78-5 java.util.concurrent.atomic을 이용한 락-프리 동기화
private static final AtomicLong nextSerialNum = new AtomicLong();
public static long generateSerialNumber() {
return nextSerialNum.getAndIncrement();
}
79. 과도한 동기화는 피하라
// 79-1 잘못된 코드. 동기화 블록 안에서 외계인 메서드를 호출한다.
public class ObservableSet<E> extends ForwardingSet<E> {
public ObservableSet(Set<E> set) {
super(set);
}
private final List<SetObserver<E>> observers = new ArrayList<>();
public void addObserver(SetObserver<E> observer) {
synchronized(observers) {
observers.add(observer);
}
}
public boolean removeObserver(SetObserver<E> observer) {
synchronized(observers) {
return observers.remove(observer);
}
}
private void notifyElementAdded(E element) {
synchronized(observers) {
for (SetObserver<E> observer : observers)
**observer.added(this, element);**
}
}
@Override public boolean add(E element) {
boolean added = super.add(element);
if (added)
notifyElementAdded(element);
return added;
}
@Override public boolean addAll(Collection<? extends E> c) {
boolean result = false;
for (E element : c)
result |= add(element); // notifyElementAdded를 호출한다.
return result;
}
}
@FunctionalInterface
public interface SetObserver<E> {
// ObservableSet에 원소가 추가되면 호출된다.
void added(ObservableSet<E> set, E element);
}
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<>() {
public void added(ObservableSet<Integer> s, Integer e) {
System.out.println(e);
**if ( e == 23)**
**s.removeObserver(this);**
}
});
// 79-3 외계인 메서드를 동기화 블록 바깥으로 옮겼다.
private void notifyElementAdded(E element) {
List<SetObserver<E>> snapshot = null;
synchronized (observers) {
snapshot = new ArrayList<>(observers);
}
for (SetObserver<E> observer : snapshot) {
observer.added(this, element);
}
}
// 79-4 CopyOnWriteArrayList를 사용해 구현한 스레드 안전하고 관찰 가능한 집합
private final List<SetObserver<E>> observers =
new CopyOnWriteArrayList<>();
public void addObserver(SetObserver<E> observer) {
observers.add(observer);
}
public boolean removeObserver(SetObserver<E> observer) {
return observers.remove(observer);
}
private void notifyElementAdded(E element) {
for (SetObserver<E> observer : observers)
observer.added(this, element);
}
80. 스레드보다는 실행자, 태스크, 스트림을 애용하라
ExecutorService exec = Executors.newSingleThreadExecutor(); // 작업 큐를 생성하는 방법
exec.execute(runnable); // 실행자에 실행할 테스트를 넘기는 방법
exec.shutdown(); // 우하하게 종료시키는 방법
81. wait와 notify보다는 동시성 유틸리티를 애용하라
// 81-1 ConcurrentMap으로 구현한 동시성 정규화 맵 - 최적은 아니다.
private static final ConcurrentMap<String, String> map =
new ConcurrentHashMap<>();
public static String intern(String s) {
String previousValue = map.puIfAbsent(s, s);
return previousValue == null ? s : previousValue;
}
// 81-2 ConcurrentMap으로 구현한 동시성 정규화 맵 - 더 빠르다!
public static String intern(String s) {
String result = map.get(s);
if (result == null) {
result = map.puIfAbsent(s, s);
if(result == null)
result = s;
}
return result;
}
// 81-3 동시 실행 시간을 재는 간단한 프레임워크
public static long time(Executor executor, int concurrency, Runnable action) throws InterruptedException {
CountDownLatch ready = new CountDownLatch(concurrency);
CountDownLatch start = new CountDownLatch(1);
CountDownLatch done = new CountDownLatch(concurrency);
for (int i = 0; i < concurrency; i++) {
executor.execute(() -> {
// 타이머에게 준비를 마쳤음을 알린다.
ready.countDown();
try {
// 모든 작업자 스레드가 준비될 때까지 기다린다.
start.await();
action.run();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 타이머에게 작업을 마쳤음을 알린다.
done.countDown();
}
});
}
ready.await(); // 모든 작업자가 준비될 때까지 기다린다.
long startNanos = System.nanoTime();
start.countDown(); // 작업자들을 깨운다.
done.await(); // 모든 작업자가 일을 끝마치기를 기다린다.
return System.nanoTime() - startNanos;
}
// 81-4 wait 메서드를 사용하는 표준 방식
synchronized (obj){
while(<조건이 충족되지 않았다>)
obj.wait(); // 락을 걸어 놓고, 깨어나면 다시 잡는다.
...// 조건이 충족됐을 때의 동작을 수행한다.
}
82. 스레드 안전성 수준을 문서화하라
// synchronizedMap이 반환한 맵의 컬렉션 뷰를 순회하려면
// 반드시 그 맵을 락으로 사용해 수동으로 동기화 하라.
Map<K, V> m = Collections.synchronizedMap(new HashMap<>());
Set<K> s = m.keySet(); // 동기화 블록 밖에 있어도 된다.
synchronized(m) { // s가 아닌 m을 사용해 동기화해야 한다!
for (K key : s)
key.f();
}
// 이대로 따르지 않으면 동작을 예츨할 수 없다.
// 82-1 비공개 락 객체 관용구 - 서비스 거부 공격을 막아준다.
private final Object lock = new Object();
public void foo() {
synchronized(lock) {
...
}
}
83. 지연 초기화는 신중히 사용하라
// 83-1 인스턴스 필드를 초기화하는 일반적인 기법
private final FieldType field = computeFieldValue();
// 83-2 인스턴스 필드의 지연 초기화 - synchronized 접근자 방식
private FieldType field;
private synchronized FieldType getField() {
if (field == null)
field = computeFieldValue();
return field;
}
// 83-3 정적 필드용 지연 초기화 홀더 클래스 관용구
// 성능 때문에 정적 필드를 지연 초기화해야 한다면 지연 초기화 홀더 클래스(lazy initialization holder class) 관용구를 사용하자
private static class FieldHolder {
static final FieldType field = computeFieldValue();
}
private static FieldType getField() { return FieldHolder.field; }
// 83-4 인스턴스 필드 지연 초기화용 이중검사 관용구
private volatile FieldType field;
private FieldType getField() {
FieldType result = field;
if (result != null) { // 첫 번째 검사 (락 사용 안함)
return result;
synchronized(this) {
if (field == null) // 두 번째 검사(락 사용)
field = computeFieldValue();
return field;
}
}
//83-5 단일검사 관용구 - 초기화가 중복해서 일어날 수 있다.
private volatile FieldType field;
private FieldType getField() {
FieldType result = field;
if (result == null)
field5 = result = computeFieldValue();
return result;
}
84. 프로그램의 동작을 스레드 스케줄러에 기대지 말라
// 84-1 끔찍한 CountDownLatch 구현 - 바쁜 대기 버전! (447쪽)
public class SlowCountDownLatch {
private int count;
public SlowCountDownLatch(int count) {
if (count < 0) {
throw new IllegalArgumentException(count + " < 0");
}
this.count = count;
}
public void await() {
**while (true) {
synchronized (this) {
if (count == 0) {
return;
}
}
}**
}
public synchronized void countDown() {
if (count != 0) {
count--;
}
}
}