Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Archives
Today
Total
관리 메뉴

전공공부

[아이템 84] 프로그램의 동작을 스레드 스케줄러에 기대지 말라 본문

Study/Java

[아이템 84] 프로그램의 동작을 스레드 스케줄러에 기대지 말라

monitor 2023. 7. 23. 18:35
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--;
        }
    }
}

10만건 실행 했을때도 slowdownlatch가 더 빨랐다.

 

시간 차이가 없거나 slowdownlatch가 더 빨랐다...

Thread yield

Thread.yield를 쓰는 것은 해결방법이 되지 못한다.

 

처음 JVM에서는 높은 성능을 보여주다가도 두번째 JVM에서는 그렇지 못해서 이식성이 좋지 않을 수 있다.

 

또한, 이를 테스트 할 수 있는 수단도 존재하지 않으므로 이 옵션은 피하자.

 

결론 : Thread.yield와 스레드 우선순위에 의존하지 말자, 프로그램 동작을 스레드 스케줄러에 기대지 말자