You will be fine

<이펙티브 자바> 11. 동기화

by BFine
반응형

78. 공유 중인 가변 데이터는 동기화해서 사용해라

  • Synchronized 키워드는 메서드나 블록을 한번에 한스레드씩 수행하도록 보장
  • 동기화 없이는 스레드가 만든 변화를 다른 스레드가 확인 못할 수 있음
  • Thread.stop 은 쓰지말자
  • 얼마나 오래 실행될까

      public class Stop{
        static boolean stop;
    
        public static void main(){
    
          Thread bg = new Thread(()->{
            int i = 0;
            //if(!stop)
            while (!stop) i++;
          })
          bg.start();
          TimeUnit.SECONDS.sleep(1); // 내부적으로 Thread.sleep 사용
          stopRequested = true;
        }
    
      }
  • 아래 처럼 변경하면 정상동작 한다.

      public class Stop{
        static boolean stop;
        //static volatile boolean stop;
        private static synchronized void requestStop(){
          stopRequested = true;
        }
        private static synchronized void stopRequested(){
          return stopRequested;
        }
    
        public static void main(){
    
          Thread bg = new Thread(()->{
            int i = 0;
            while (!stopRequested()) i++;
          })
          bg.start();
          TimeUnit.SECONDS.sleep(1); // 내부적으로 Thread.sleep 사용
          requestStop();
        }
    
      }
  • 가변데이터는 단일 스레드에서만 쓰도록하자

  • AtomicLong을 사용해보자

    • 락이 없이 스레드에 안전한 프로그래밍을 지원한다.

    • Volatile은 두효과중 통신만 지원하지만 이 패키지는 원자성까지 지원한다.

    • getAndIncrement를 사용하면 같은효과를 낸다

  • Safety failure

      volatile int next = 0;
    
      public static int genNext(){
        return next++;
      }
    • 증가 연산자는 필드에 두번 접근한다 -> 현재값을 읽고 그다음 1을 증가 시킨다

    • 만약 중간에 다른스레드가 들어올경우 같은값을 반환할 여지가 생기면서 문제가 된다 -> synchronized 하자!

  • volatile 을 선언하면 동기화를 생략해도 된다.

  • ❗️읽고 쓰기 모두 동기화 해야 올바르게 동작한다

    • 배타적 수행

    • 스레드간 통신

  • 최적화 : 끌어올리기 기법 -> if(!stop)을 추가한다? -> 응답불가

  • 메인스레드가 수정한 값을 백그라운드 스레드가 언제 보게될지 보증할 수 없다!!

78. 과도한 동기화는 피하자

  • 성능을 떨어뜨리고 교착상태에 빠뜨린다.

  • 응답불가와 안전실패를 피하려면 동기화 메서드나 동기화 블록 안에서는 제어를 절대로 클라이언트에 양도하면 안된다.

    • 재정의 할수있는 메서드 호출 안됨

79. 스레드보다는 실행자, 태스크, 스트림을 쓰자

  • 뛰어난 작업큐를 한줄로 생성할 수 있다.

    • ExecutorService exec = Excutors.newSingleThreadExecutor();
    • exec.excute(runnable) , exec.shutdown();
  • CashedThreadPool은 무거운 프로덕션 서버에는 좋지 못하다.

    • 요청받은 태스트들이 큐에 쌓이지 않고 즉시 스레드에 위임
    • 가용한 스레드가 없으면 새로 하나 생성
  • 무거운 프로덕션 서버에서는 스레드개수를 고정한 Executors.newFixedThreadPool 이나 통제가능한 ThreadPoolExecutor를 사용하자

  • 작업큐를 손수 만들거나 스레드를 직접다루는것은 삼가해야한다.

  • 실행자 프레임워크에서는 작업단위와 실행 메커니즘이 분리된다.

    • 작업단위를 나타내는 핵심 추상 개념이 태스크다.

      • Runnable
      • Callable(값을 반환하고 임의의 예외를 던질수 있다)
    • 태스크를 실행하는 일반적인 메커니즘이 실행자 서비스 이다.

  • 자바 7로 오면서 fork join 테스크를 지원하도록 확장

    • 포크조인 풀이라는 특한한 실행자 서비스가 실행해준다
    • 일을 잘게 분할해서 (하위 태스크로 나눔) 일이 끝난 스레드는 다른스레드의 남은 태스크를 가져와서 일을 처리할 수 도 있다.
    • CPU를 최대한 활용하여 낮은 지연 및 높은 처리량을 달성할 수 있다.

81. wait과 notify 보다는 동시성 유틸리티를 사용하자

  • 올바르게 사용하기 까다롭다.

  • util.concurrent의 고수준 유틸리티는 세 범주로 나눌 수 있다.

    • 실행자 프레임워크, 동시성 컬렉션, 동기화 장치
    • 동시성 컬렉션 : 표준 컬렉션(List, Map 등) 인터페이스에 동시성을 가미
      • 동기화를 각자 내부에서 수행, 외부에서 락을 또쓰면 속도가 느려짐
  • ConcurrentHashMap은 동시성 및 속도도 무척 빠르다

    • synchronizedMap 보다 훨씬 성능이 좋다

    • 내부를 보면 일부에만 syn가 걸려있다

    • putIfAbsent

        for(AbstractHashedMap.HashEntry entry = this.data[this.hashIndex(hashCode,this.data.length)]; 
             entry != null;entry = entry.next) {
              if (entry.hashCode == hashCode && this.isEqualKey(key, entry.key)) {
                  return true;
              }
          }
      
          return false;
      }
      
      public boolean containsKey(Object key) {
      
          key = this.convertKey(key);
          int hashCode = this.hash(key);
  • BlockingQueue는 큐가 비어있으면 새로운 원소가 추가 될때까지 기다린다.

    • 생상자- 소비자큐로 쓰기에 적합
  • 자주 쓰이는 동기화 장치는 CountDownLatch와 Semaphore, Phaser(가장 강력한 동기화)

    • 래치는 일회성 장벽으로 하나이상의 스레드가 또다른 하나이상의 스레드의 작업이 끝날때까지 기다리게한다.

      • 생성자는 int값을 받고 래치의 countdown 메서드를 몇번호출해야 대기중인 스레드를 깨우는지결정한다.
  • 시간간격을 잴때는 System.nanoTime을 사용하자

  • wait 메서드를 사용할때는 반드시 wait loop를 사용하자, 반복분 밖에서는 절대 호출하지 말자

  • 일반적으로 notfiyAll을 사용하는게 합리적으로 안전한 조언이 될것이다.

82. 스레드 안전성 수준을 문서화하라

83. 지연초기화는 신중히 사용하라

  • 필드의 초기화 시점을 그값이 처음 필요할때까지 늦추는 기법이다.

    • 그값이 안쓰이면 초기화도 일어나지 않는다
  • 주로 최적화 용도이고 클래스와 인스턴스 초기화때 발생하는 위험한 순환문제를 해결하는 효과도있다.

  • 초기화 비용은 줄지만 지연초기화하는 필드에 접근하는 비용은 커진다.

  • 성능때문에 정적필드를 지연초기화 해야한다면 지연초기화 홀더 클래스 관용구를 사용하자

      static Class FieldHolder{
        static FieldType field = computeFiledValue();
      }
      private static FieldType getField(){return FieldHolder.field;}
    • 정확성이나 성능이 스레드 스케줄러에 따라 달라지는 프로그램이면 다른 플랫폼에 이식하기 어렵다.

    • 스레드는 당장 처리해야할 작업이 없다면 실행돼서는 안된다

      • 공유객체의 상태가 바뀔때까지 쉬지않고 검사해서는 안된다는 뜻이다
    • Thread.yield는 테스트할 수단도 없다.

반응형

블로그의 정보

57개월 BackEnd

BFine

활동하기