Book

[모던 자바 인 액션] 1장. 자바 8, 9, 10, 11 : 무슨 일이 일어나고 있는가?

Jordy-torvalds 2021. 9. 28. 16:02
반응형

개요

멀티코어 CPU 대중화와 같은 하드웨어적인 변화가 자바8에 영향을 미쳤다. CPU 속도 증가에 한계점에 이르며 코어 수를 높이는 쪽으로 하드웨어가 발전해갔는데 이러한 변화에 발맞춰 자바 또한 변화해간 것이다.

또한 빅데이터(테라바이트 이상의 데이터셋)라는 도전에 직면하면서 멀티코어 서버나 클러스터를 이용하여 이를 효율적으로 처리 해야 됐다. 그러나 자바 8 이전의 자바로는 충분히 대응하기가 어려웠다.

물론 자바가 병렬 처리를 위한 API를 완전히 제공하지 않아왔던 것은 아니다.

자바 5에서는 쓰레드 풀, 병렬 실행 컬렉션을 도입했고 자바7에는 포크/조인 프레임워크을 제공했으나 개발자가 사용하기가 쉽지 않았다. 이에 반해 자바8은 병렬 실행을 새롭고 단순한 방식으로 접근할 수 있는 방법을 제공한다.

자바 8 설계의 밑바탕에는 세 가지 프로그래밍 개념이 있다.

자바 8 설계의 밑바탕

첫번째, 스트림 처리

스트림한 번에 한 개씩 만들어지는 연속적인 데이터 항목들의 모임이다.

이론적으로 프로그램은 입력 스트림에서 데이터를 하나씩 읽어들이며 마찬가지로 출력 스트림으로 데이터를 한 개씩 기록한다.

즉, 어떤 프로그램의 출력 스트림은 다른 프로그램의 입력 스트림이다.

그렇기 때문에 스트림을 파이프라인처럼 연결해서 일련의 데이터 가공 과정을 통해 정제하는 것이 가능하고, 스레드라는 복잡한 작업을 사용하지 않아도 parallel이란 키워드가 들어간 함수를 통해 병렬성을 얻을 수 있다.(후술하겠지만 사용상의 주의가 필요하다)

두번째, 동작 파라미터화(behavior parameterization)

자바8에서 추가된 동작 파라미터화를 통해 코드 일부를 API를 전달할 수 있게 되었다!

동적 파라미터화는 어떤 로직을 처리할 지 확정하지 않고, 이에 대한 결정을 클라이언트에게 위임한다. 덕분에 중복을 방지할 수도 있고, 소스코드의 유연성도 높일 수 가 있다.

뿐만 아니라 메소드에 처리 내용을 파라미터로 전달해서 메소드 내에 특정 시점에 실행해서 처리하는 것이 가능하다.

자바 8 이전에는 원시 변수나 참조 변수만이 메소드에 전달 가능한 일급 값이었고 메서드 및 처리 로직들은 넘길 수 없는 이급 값이였다.

그러나 동적 파라미터화를 통해 메서드 및 처리 로직들 또한 일급 값이 되었다.

세번째, 병렬성과 공유 가변 데이터

스트림에서 지원하는 parallel로 시작하는 함수를 통해 병렬 처리를 간편하게 할 수 있다. 다만, 안전하고 의도한 대로 동작하려면 사용상의 주의가 필요하다. 이를 위해 책에서 강조하는 것 중 하나가 공유된 가변 데이터에 대한 접근 지양 이다.

변화가 가능하다는 것은 유연함을 의미하기도 하지만, 사이드 이펙트를 의미하기도 한다. 그러나 병렬성을 얻으려면 이러한 공유된 가변 데이터에 대해 지양하는 것이 필요하다. 상태 없고 부작용 없는 함수가 되어야 한다는 것이다.

주의를 거쳐 사용되는 병렬 처리는 아주 강력하다.

아래 코드를 실행해보면 콘솔에 출력된 각 테스트의 소요 시간 차이를 비교해볼 수 있다.

class StreamExample {
        private static final int TEST_MAX_RANGE = 30_000_000;

        public void example() {
                test("int-stream", () -> {
                    IntStream.rangeClosed(1, TEST_MAX_RANGE).sum();
                });

                test("int-parallel-stream", () -> {
                    IntStream.rangeClosed(1, TEST_MAX_RANGE).parallel().sum();
                });
        }

        private void test(String label, Consumer<List<Integer>> function) {
        long current = currentTimeMillis();
        function.accept(list);
        long end = currentTimeMillis();
        System.out.println(label + "    " + (end - current));
    }
}

디폴트 메소드

기존 인터페이스에 메소드가 추가되면 해당 인터페이스를 구현하는 클래스는 추가된 메소드에 구현이 강제된다. 그러나 이를 원하지 않을 경우 디폴트 메소트로 구현함으로써 회피할 수 있다.

아래는 자바 8의 List 인터페이스에 구현된 sort 메소드다.

디폴트 메소드 덕분에 List의 구현체인 ArrayList나 LinkedList는 별도로 sort 메소드를 구현하지 않아도 사용자들은 List 인터페이스에 구현된 sort 메소드를 사용함으로서 정렬을 할 수 있다.

반응형