전공공부
[아이템 84] 프로그램의 동작을 스레드 스케줄러에 기대지 말라 본문
OS Thread Scheduler
여러 스레드가 실행 중이면 운영체제의 스레드 스케줄러가 어떤 스레드를 얼마나 오래 실행할지 정한다. 정상적인 운영체제라면 이 작업을 공정하게 수행하지만 구체적인 스케줄링 정책은 운영체제마다 다를 수 있다.
따라서 잘 작성된 프로그램이라면 이 정책에 좌지우지돼서는 안된다. (OS에 따라서 성능이 달라지면 안됌.)
정확성이나 성능이 스레드 스케줄러에 따라 달라지는 프로그램이라면 다른 플랫폼에 이식하기 어렵다.
견고하고 이식성이 좋은 프로그램을 만드는 법
해결방법 : 실행가능한 스레드의 평균적인 수를 프로세서 수보다 지나치게 많아지지 않도록 설계해야 한다.
그러면, 실행가능한 스레드의 수를 적게 유지하는 방법은 없을까?
각 스레드가 일을 마치면 다음 일거리가 생길때 까지 대기 시키는게 하나의 방법이 될 수 있다. 적절히 스레드 풀 사이즈를 설정하고 작업은 짧게 유지하면 된다.
Busy Waiting 상태가 되면 안된다.
공유 객체의 상태가 바뀔때 까지 기다려서는 안된다. 아래 예제 처럼
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--;
}
}
더보기
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Item84 {
public static void main(String args[]) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1000);
List<Thread> workers = Stream
.generate(() -> new Thread(new Worker(countDownLatch)))
.limit(1000)
.collect(Collectors.toList());
long startTime = System.currentTimeMillis();
workers.forEach(Thread::start);
countDownLatch.await();
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
SlowCountDownLatch slowCountDownLatch = new SlowCountDownLatch(1000);
List<Thread> workers2 = Stream
.generate(() -> new Thread(new slowWorker(slowCountDownLatch)))
.limit(1000)
.collect(Collectors.toList());
long startTime2 = System.currentTimeMillis();
workers2.forEach(Thread::start);
slowCountDownLatch.await();
long endTime2 = System.currentTimeMillis();
long executionTime2 = endTime2 - startTime2;
System.out.println(" Java CountDownLatch " + executionTime +" mills"+ " slow down latch milliseconds : " + executionTime2 + " mills");
}
public static class Worker implements Runnable {
private CountDownLatch countDownLatch;
public Worker(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
countDownLatch.countDown();
}
}
public static class slowWorker implements Runnable {
private SlowCountDownLatch slowCountDownLatch;
public slowWorker(SlowCountDownLatch slowCountDownLatch) {
this.slowCountDownLatch = slowCountDownLatch;
}
@Override
public void run() {
slowCountDownLatch.countDown();
}
}
public static 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--;
}
}
}
Thread yield
Thread.yield를 쓰는 것은 해결방법이 되지 못한다.
처음 JVM에서는 높은 성능을 보여주다가도 두번째 JVM에서는 그렇지 못해서 이식성이 좋지 않을 수 있다.
또한, 이를 테스트 할 수 있는 수단도 존재하지 않으므로 이 옵션은 피하자.
결론 : Thread.yield와 스레드 우선순위에 의존하지 말자, 프로그램 동작을 스레드 스케줄러에 기대지 말자
'Study > Java' 카테고리의 다른 글
ObjectMapper를 이용한 LocalDateTime Deserialize 에러 잡기 (0) | 2023.08.03 |
---|---|
[아이템 88] readObject는 방어적으로 작성하라. (0) | 2023.07.31 |
[아이템 79.] 과도한 동기화는 피하라 (0) | 2023.07.10 |
[아이템 74.] 메서드가 던지는 모든 예외를 문서화하라 (0) | 2023.07.04 |
[아이템 70] 복구할 수 있는 상황에서는 검사 예외를, 프로그래밍 오류에는 런타임 예외를 사용하라 (0) | 2023.06.25 |