스터디/이펙티브 자바 스터디

이펙티브 자바 스터디 - 11주차

딱구킴 2022. 12. 31. 22:22

스터디 깃허브 : https://github.com/SeolYoungKim/effective_java_study

  • 5명이서 일주일마다 1인 1아이템 정리 및 발표

소스코드 깃허브 : https://github.com/SeolYoungKim/effective-java-example

이펙티브 자바 스터디 11주차

개인 주제 | 아이템 59. 라이브러리를 익히고 사용하라

라이브러리를 익히고 사용하라

표준 라이브러리 사용 시 누릴 수 있는 이점

  • 그 코드를 작성한 전문가의 지식과 여러분보다 앞서 사용한 다른 프로그래머들의 경험을 활용할 수 있다.
  • 핵심적인 일과 크게 관련 없는 문제를 해결하느라 시간을 허비하지 않아도 된다.
  • 따로 노력하지 않아도 성능이 지속해서 개선된다.
  • 기능이 점점 많아진다.
  • 우리가 작성한 코드가 많은 사람에게 낯익은 코드가 된다.
    • 더 읽기 좋고, 유지보수하기 좋으며, 재활용하기 쉬운 코드가 된다.

위의 이점을 생각해서라도 라이브러리를 익히자

  • 메이저 릴리스마다 주목할만한 수많은 기능이 라이브러리에 추가된다.
  • 릴리스마다 새로운 기능을 설명하는 웹 페이지를 공시하니 읽어볼 것을 권장한다.

예를 들어 위와 같은 기능이 있다.(transferTo)

  • 이는 지정한 URL의 내용을 가져오는 기능이며, 자바 9에서 추가됐다.
  • 예전에는 작성하기가 까다로운 기능이었지만, 라이브러리에서 제공 해줌으로써 손쉽게 사용할 수 있다.

자바 프로그래머라면

  • java.lang
  • java.util
  • java.io

위 세 가지의 라이브러리와 하위 패키지들에는 익숙해지자. 다른 라이브러리들은 필요할 때마다 익히면 된다.

다른 라이브러리 중에서

  • 컬렉션 프레임워크
  • 스트림 라이브러리
  • 동시성 기능

위 세 가지도 알아두면 큰 도움이 된다.


정리

  • 우선은 라이브러리를 사용하도록 시도해라
    • 없다면 대안을 사용해라 (고품질의 서드파티 라이브러리 등)
      • 구글 구아바 라이브러리가 대표적
  • 바퀴를 다시 발명하지 말자.
  • 라이브러리를 쓰자. 있는지 모르겠다면 찾아보고 쓰자.

나의 생각

있는 라이브러리를 사용하는 것도 중요하지만, 잘 알고 쓰는 것도 중요한 것 같습니다. 저는 QueryDSL의 _super키워드가 있는지 몰라서, JPA에 대해 잘 알지 못한 채 써서, 리플렉션에 대해 잘 알지 못한 채 써서 몇 시간을 삽질한 경험이 있습니다.

그럴 때 마다 느끼는 것은 잘 알고 사용하자 인데, 사실 "잘 알고"가 조금 어려운 것 같습니다. 상세한 내용들까진 아니더라도, 기본적인 개념이나 아키텍처라도 잘 알고 쓴다면, 문제 해결에 도움이 되지 않을까 생각합니다.


책에서 나온 소소한(?) 팁

  • 자바 7부터는 Random보다는 ThreadLocalRandom으로 대체하라.
    • 더 빠르고 고품질의 난수를 생성한다.
    • 병렬 스트림이나 포크조인 풀에서는 SplittableRandom사용

책에서, 위와 같이 Random을 사용하지 말고 ThreadLocalRandom을 사용할 것을 권고하고 있습니다. 더 빠르기 때문이라는데.. 왜 더 빠를 수 있는걸까 궁금해서 좀 더 찾아보았습니다.

 

ThreadLocalRandom이 더 빠를 수 있는 이유는, Random에는 스레드 경합 지점이 될 수 있는 부분이 있기 때문인 것으로 추측됩니다. 어디가 스레드 경합이 발생하는지 한번 살펴보겠습니다.

 

RandomnextInt()입니다.

public int nextInt() {
    return next(32);
}

 

해당 메서드 안에 있는 next()입니다.

protected int next(int bits) {
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    do {
        oldseed = seed.get();
        nextseed = (oldseed * multiplier + addend) & mask;
    } while (!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> (48 - bits));
}

내부에 AtomicLong이 있습니다. 이는 스레드에 안전하도록 만든 클래스이며, concurrent 패키지에 있습니다. Atomic 클래스들은 "낙관적 락"기법을 사용하여 스레드에 안전하도록 만들었다고 합니다.

 

비관적 락 방식은 애초에 락이 걸려있으면 진입을 못하는 반면, 낙관적 락 방식은 일단 진입하고 봅니다. AtomicLongcompareAndSet을 통해 낙관적 락을 구현했다고 합니다.

 

해당 메서드는 네이티브 메서드로, 정확한 기작은 확인할 수 없었지만, 대략적으로 다음과 같이 동작한다고 합니다.

  • 가지고 있었어야 할 값을 그대로 가지고 있다면 수정을 한다.
    • 기대 했던 값과 일치함 -> 업무를 수행함
    • 기대 했던 값과 다름 -> 업무를 수행하지 않음 (예외/재시도 등을 해야함)

따라서, 다음과 같이 동작합니다.

  • compareAndSet이 실패하면 계속 반복 -> 새로운 값을 가져와서 반복
  • 스레드가 동시에 접근했다면 둘중 하나는 무조건 실패함 -> 재시도

자세한 원리를 파악하기엔, 아직 동시성이라는 개념이 익숙하지가 않아 어려움이 있었습니다. 하지만, 중요한 점은 "두 개의 스레드가 해당 지점에서 경합이 발생하면 무조건 하나는 실패하고 재시도 한다."라는 것입니다. 즉, 이 부분에서 성능 이슈가 발생할 수 있다고 합니다.

해당 문제는 ThreadLocal을 사용하도록 만든 ThreadLocalRandom을 도입하면 해결할 수 있다고 합니다.

 

  • ThreadLocal이란
    • 스레드 범위의 변수, 즉 스레드 scope를 가지는 변수를 의미합니다. 각 스레드마다 독립적으로 사용할 수 있는 변수입니다.
    • 사용처 : Spring의 트랜잭션 AOP가 커넥션을 관리할 때 사용함. (트랜잭션 동기화 매니저)

 

해당 클래스는 다음과 같이 동작함으로써, 스레드 경합을 피할 수 있습니다.

  • 현재 스레드에 할당되어있는 랜덤을 가져옴 -> 스레드마다 독립적이기 때문에 경합이 발생할 일이 없음 -> 성능 개선

아무래도 스레드 경합, 데드락 등의 문제로 인해 저자는 ThreadLocalRandom을 사용하라고 한 것 같습니다. 이에 따라, 단일 스레드 환경에서 랜덤을 사용한다 하더라도 ThreadLocalRandom을 사용하는 습관을 들이면 좋을 것 같습니다.