Error rendering WebPanel: No renderer found for resource type: velocity Template contents: <meta name="ajs-keyboardshortcut-hash" content="$keyboardShortcutManager.shortcutsHash">

버전 비교

  • 이 줄이 추가되었습니다.
  • 이 줄이 삭제되었습니다.
  • 서식이 변경되었습니다.

...

  • 스트림 파이프라인 : 데이터 원소들로 수행하는 연산 단계 표현

    • Stream.of(1,2,3,4).filter(value -> value / 2 == 0).filter( value -> value < 3).count()

    • (Single) Stream Generator 

    • (0 or More) 중간연산(intermediate operation) : 스트림을 어떠한 방식으로 변환하는 연산

      • filter(Predicate<? super T> predicate) : predicate 함수에 맞는 요소만 사용하도록 필터

      • map(Function<? Super T, ? extends R> function) : 요소 각각의 function 적용

      • flatMap(Function<? Super T, ? extends R> function) : 스트림의 스트림을 하나의 스트림으로 변환

      • distinct() : 중복 요소 제거

      • sort() : 기본 정렬

      • sort(Comparator<? super T> comparator) : comparator 함수를 이용하여 정렬

      • skip(long n) : n개 만큼의 스트림 요소 건너뜀

      • limit(long maxSize) : maxSize 갯수만큼만 출력

    • (Single) 종단연산(terminal operation) : 마지막 중간 연산이 내놓은 스트림에 가하는 마지막 연산

      • forEach(Consumer<? super T> consumer) : Stream의 요소를 순회

      • count() : 스트림 내의 요소 수 반환

      • max(Comparator<? super T> comparator) : 스트림 내의 최대 값 반환

      • min(Comparator<? super T> comparator) : 스트림 내의 최소 값 반환

      • allMatch(Predicate<? super T> predicate) : 스트림 내에 모든 요소가 predicate 함수에 만족할 경우 true

      • anyMatch(Predicate<? super T> predicate) : 스트림 내에 하나의 요소라도 predicate 함수에 만족할 경우 true

      • noneMatch(Predicate<? super T> predicate) : 스트림 내에 모든 요소가 predicate 함수에 만족하지않는 경우 true

      • sum() : 스트림 내의 요소의 합 (IntStream, LongStream, DoubleStream)

      • average() : 스트림 내의 요소의 평균 (IntStream, LongStream, DoubleStream)


    • 종단연산을 빼먹으면 동작하지 않는다.
    • 중간 연산이 여러개 존재할 경우 종단 연산에서 지연 평가된다.

      • 지연평가(lazy evaluation) : 계산의 결과값이 필요할 때까지 계산을 늦추는 기법

  • 스트림을 과용하지 마라(you can doesn't mean you should!, happy medium!)

    • 과용하면 가독성이 떨어지고 유지보수가 어려워짐(probablly느려짐)

    • 기존 코드는 스트림을 사용하도록 리펙토링하되, 새 코드가 더 나아보일 때만 반영

      코드 블럭
      languagejava
      themeMidnight
      firstline1
      title사전File에서 단어를 읽어 사용자가 지정한 값보다 원소수가 많은 Anagroup 출력
      linenumberstrue
      collapsetrue
      public class IterativeAnagrams {
      	// anagram = test, tets 등
          // 사용자가 지정한 minGroupSize 값보다 원소 수가 많은 anagram 그룹을 출력한다.
          public static void main(String[] args) throws IOException {
              File dictionary = new File(args[0]);
              int minGroupSize = Integer.parseInt(args[1]);
      
      		// Key = anagram, Value : 같은 anagram인 단어의 집합 Set
              Map<String, Set<String>> groups = new HashMap<>();
              try (Scanner s = new Scanner(dictionary)) {
                  while (s.hasNext()) {
                      String word = s.next();
      				// (1) Map 안에 Key가 있는지 찾음
      				// (2-1)있으면 단순히 Key mapping 된 Value 반환
      				// (2-2)없으면 함수 객체를 Key에 적용하여 Value를 계산
      				// (3) Key와 계산된 Value를 mapping하고, 계산된 value를 반환
      				// * computeIfAbsent : 맵 안에 키가 있는지 찾은 다음, 있으면 단순히 그 키에 매핑된 값을 반환한다.(java8+)
                      groups.computeIfAbsent(alphabetize(word),
                              (unused) -> new TreeSet<>()).add(word);
                  }
              }
      
              for (Set<String> group : groups.values())
                  if (group.size() >= minGroupSize)
                      System.out.println(group.size() + ": " + group);
          }
      
          private static String alphabetize(String s) {
              char[] a = s.toCharArray();
              Arrays.sort(a);
              return new String(a);
          }
      }
      
      
      /* alphabetize까지 변환 */
      public class StreamAnagrams {
          public static void main(String[] args) throws IOException {
              Path dictionary = Paths.get(args[0]);
              int minGroupSize = Integer.parseInt(args[1]);
      
      
              try (Stream<String> words = Files.lines(dictionary)) {
                  words.collect(
                          groupingBy(word -> word.chars().sorted()
                                  .collect(StringBuilder::new,
                                          (sb, c) -> sb.append((char) c),
                                          StringBuilder::append).toString()))
                          .values().stream()
                          .filter(group -> group.size() >= minGroupSize)
                          .map(group -> group.size() + ": " + group)
                          .forEach(System.out::println);
              }
          }
      }
      
      /* alphabetize 제외 Stream 변환 : 적절 */
      public class HybridAnagrams {
          public static void main(String[] args) throws IOException {
              Path dictionary = Paths.get(args[0]);
              int minGroupSize = Integer.parseInt(args[1]);
      
      		//groupBy : 알파벳화한 단어를 알파벳화 결과가 같은 단어들의 리스트로 매핑하는 맵을 생성
              try (Stream<String> words = Files.lines(dictionary)) {
                  words.collect(groupingBy(word -> alphabetize(word)))
                          .values().stream()
                          .filter(group -> group.size() >= minGroupSize)
                          .forEach(g -> System.out.println(g.size() + ": " + g));
              }
          }
      
          private static String alphabetize(String s) {
              char[] a = s.toCharArray();
              Arrays.sort(a);
              return new String(a);
          }
      }
    • char용 스트림을 지원하지 않기 때문에 char값을 처리할 때는 스트림을 삼가는 것이 좋음

      코드 블럭
      languagejava
      themeMidnight
      firstline1
      titlechar 스트림
      linenumberstrue
      collapsetrue
      public class CharStream {
          public static void main(String[] args) {
              // Does not produce the expected result
      		// 결과 값은 '721011081081113211911111410810033' 출력
      		// String's char method returns IntStream
              "Hello world!".chars().forEach(System.out::print);
              System.out.println();
      
      
              // Fixes the problem
      		// 결과 값 'Hello world!' 출력
              "Hello world!".chars().forEach(x -> System.out.print((char) x));
              System.out.println();
          }
      }



  • 스트림 권장

    • 원소들의 시퀀스를 일관되게 변환

    • 원소들의 시퀀스를 필터링

    • 원소들의 시퀀스를 하나의 연산을 사용해 결합(더하기, 연결하기, 최솟값 구하기)

    • 원소들의 시퀀스를 하나의 collection에 모음 (공통된 속성을 기준으로 묶어가며)

    • 원소들의 시퀀스에서 특정조건을 만족하는 원소를 찾음


  • 스트림을 권장하지 않는 경우

    • 값에 동시에 접근해야할 때
      • 한 스트림 파이프라인(peek)에서 각 단계의 값들에 동시에 접근 할 수 없음(한 데이터가 파이프라인의 여러 단계를 통과할 때)
      • 스트림 파이프라인은 일단 한 값을 다른 값에 맵핑하고 나면 원래의 값을 잃음(원래 값을 다른 곳에 저장 가능하지만, 지저분해 짐)
    코드 블럭
    languagejava
    themeRDark
    firstline1
    title동시에 접근해야 할 때
    linenumberstrue
    collapsetrue
    Int[] arr = new int[]{10, 2, ,3, 4, 9};
    Int sum = Arrays.stream(arr)
    		.filter(firstValue -> firstValue % 2 == 0)
    		.filter(secondValue -> secondValue > 3)
    		.peak(secendValue -> {
    			int temp = firstValue + secondValue; //compile error
    			return secondValue;
    		}).sum();
    코드 블럭
    languagejava
    themeMidnight
    firstline1
    title개인의 취향과 프로그래밍 환경에 따라 결정하고, 둘 다 해보고 더 나아 보이는 쪽을 하라
    linenumberstrue
    collapsetrue
    public class Card {
    /*카드는 숫자(rank)와 무늬(suit)를 묶은 불변 값 클래스이고 숫자와 무늬는 열거타입이다. */    
    	public enum Suit { SPADE, HEART, DIAMOND, CLUB }
        public enum Rank { ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN,
                           EIGHT, NINE, TEN, JACK, QUEEN, KING }
    
    
        private final Suit suit;
        private final Rank rank;
    
        @Override public String toString() {
            return rank + " of " + suit + "S";
        }
    
        public Card(Suit suit, Rank rank) {
            this.suit = suit;
            this.rank = rank;
    
        }
        private static final List<Card> NEW_DECK = newDeck1(); //or newDeck2()
    
        // (1) for-each 사용 / 데카르트 곱 계산을 반복 방식으로 구현
        private static List<Card> newDeck1() {
            List<Card> result = new ArrayList<>();
            for (Suit suit : Suit.values())
                for (Rank rank : Rank.values())
                    result.add(new Card(suit, rank));
            return result;
        }
        // (2) 스트림을 중복하여 사용
        private static List<Card> newDeck2() {
            return Stream.of(Suit.values())
                    .flatMap(suit ->  
                            Stream.of(Rank.values())
                                    .map(rank -> new Card(suit, rank)))
                    .collect(toList());
        }
    
    	//flatMap : 스트림의 원소 각각을 스트림으로 맵핑한 다음 그 스트림을 다시 하나의 스트림으로 합침(flattening)
        public static void main(String[] args) {
            System.out.println(NEW_DECK);
        }
    }
    

...

  • forEach는 잘 생각해보고 써라.
    • forEach은 종단연산 중 가장 기능이 적고, 스트림답지 않음
    • forEach 연산은 스트림 계산 결과를 보고할 때만 사용하고, 계산하는 데는 쓰지 말아 함
코드 블럭
languagejava
themeRDarkMidnight
firstline1
titleforeach 사용을 삼가라
linenumberstrue
collapsetrue
//단어 별 수를 세어 빈도표로 만드는 코드. 
public class Freq {
    public static void main(String[] args) throws FileNotFoundException {
        File file = new File(args[0]);


		/* freq1 따라하지 말 것
		Uses the streams API but not the paradigm--Don't do this!
		스트림을 가장한 반복적 코드 forEach 에서 freq 를 계속해서 수정함  */
        Map<String, Long> freq1 = new HashMap<>(); 
        try (Stream<String> words = new Scanner(file).tokens()) {
            words.forEach(word -> { 
                freq.merge(word.toLowerCase(), 1L, Long::sum);
           });
        }

        // Proper use of streams to initialize a frequency table 
        Map<String, Long> freq2;
        try (Stream<String> words = new Scanner(file).tokens()) {
            freq = words
                    .collect(groupingBy(String::toLowerCase, counting()));
				//classifier(분류함수)로 받고, Collector(수집기 = 카테고리별로 모아 놓은 맵)로 출력
				//다운스트림 수집기로 counting()을 건네 각 키(카테고리)를 카테고리에 속하는 원소의 개수와 매핑하여 맵을 얻음
				//다운스트림 수집기로 summing(), averaging(), summarizing()도 있음
        }

        System.out.println(freq2);

        // Pipeline to get a top-ten list of words from a frequency table
        List<String> topTen = freq.keySet().stream()
                .sorted(comparing(freq::get).reversed())
                .limit(10)
                .collect(toList());

        System.out.println(topTen);
    }
}

...

  • java.util.stream.Collectors를 활용하라 (현재 총 43개 메서드)
    • https://docs.oracle.com/javase/10/docs/api/java/util/stream/Collectors.html

    • Collectors를 이용하면 스트림의 원소를 쉽게 Collection으로 모을 수 있음(toList(), toSet(), toCollection())
    • toMap()
      • 스트림 원소를 key에 mapping하는 함수(valueMapper)와 value에 mapping하는 함수(keyMapper)를 인자로 받음
    • groupingBy()
      • 스트림의 요소를 그룹화하는 메서드
      • 분류함수(classifier)를 인자로 받아, 스트림의 원소들을 카테고리별로 모아놓은 map list를 반납

        • 비슷하게 분류함수자리에 prediate을 받고 boolean인 맵을 반환하는 partitionBy도 참고
    • Joining()
      • CharSequence 타입의 접두문자. 타입만 받을 수 있음

      • 단순히 원소를 연결하는 수집기 반환

      • 접두문자, 구분문자, 접미문자를 매개변수로 받음

        • 접두([), 접미(]), 구분(,)을 설정했을 경우 - [came, saw, ...] 등으로 출력


아이템 47. 반환타입으로는 스트림보다 컬렉션이 낫다

  • (~java7) Array형태의 Linear한 자료구조를 반환하는 기존 메서드 반환타입

    • 종류
      • Collection<E>, Set<E>, List<E>와 같은 컬렉션 인터페이스
      • E[]와 같은 배열
      • Iterable<E> 인터페이스
    • 기본은 Collection<E> 타입
    • for-each 문에서만 쓰이거나, (contain(Object) 같은) 일부 Collection 메서드를 구현 할 수 없을 때는 Iterable 인터페이스를 사용

    • 성능에 민감한 상황이면, E[] 형태의 배열을 주로 사용

  • (java8~) Stream 추가
    • 스트림은 반복을 지원하지
    않음
    • 그러므로 Stream과 않지 않기때문에 Stream과 반복을 알맞게 조합해야 좋은 코드
      • API를 Stream만 반환하여 사용하도록 하면 (반환하여) for-each를 사용하고자 하는 개발자는 불편을 겪을 것
        (반복문을 for-each로 처리하여 반환하길 요청하였는데, Stream으로 반환받는다면 코드처리가 지저분해 짐)
    • Stream은 Iterator 인터페이스가 정의한 추상메서드를 포함하지만(동일 동작),
      • Iterator를 확장(extend)하진 않아 for-each로 반복하지 못함
      • Stream::iterator를 형변환(Iterator<String>)해주면 for-each문 사용 가능하지만 Collection을 이용한 for-each보다는 성능이 떨어질 수 있음
      • Collection도 Iterator의 하위 타입이며, stream메서드 지원
코드 블럭
languagejava
themeMidnight
firstline1
titleAPI에서 Stream만 반환하는 경우
linenumberstrue
collapsetrue
/**
* Returns a snapshot of all processes visible to the current process.
* <p>
* <em>Note that processes are created and terminate asynchronously. There
* is no guarantee that a process in the stream is alive or that no other
* processes may have been created since the inception of the snapshot.
* </em>
*
* @return a Stream of ProcessHandles for all processes
* @throws SecurityException if a security manager has been installed and
*         it denies RuntimePermission("manageProcess")
* @throws UnsupportedOperationException if the implementation
*         does not support this operation
*/
static Stream<ProcessHandle> allProcesses() {
    return ProcessHandleImpl.children(0);
}


/* ----------------------타입추론에러로컴파일되지 않음---------------------- */
// Test.java:6 error: method reference not expected here                
for (ProcessHandle ph : ProcessHandle.allProcesses()::iterator) {
}


/* -------------오류 바로잡기 Iterable Casting - 책에는 컴파일된다고 나왔으나 컴파일 안됨---------------------- */
// ClassCastException
for(ProcessHandle ph : (Iterable<ProcessHandle>) ProcessHandle.allProcesses().iterator()) {
}




/* -------------오류 바로잡기 - 어댑터 사용 ---------------------- */
// Stream<E>를 Iterable<E>로 중개해주는 어댑터 : iterableOf 메서드를 통해 명시적으로 Iterable으로 반환
for (ProcessHandle ph : iterableOf(ProcessHandle.allProcesses()) {
}

// Adapter from  Stream<E> to Iterable<E> (
public static <E> Iterable<E> iterableOf(Stream<E> stream) {
        return stream::iterator;
}


 // Adapter from Iterable<E> to Stream<E>
public static <E> Stream<E> streamOf(Iterable<E> iterable) {
       return StreamSupport.stream(iterable.spliterator(), false);
}


    • 반환타입
      • 객체 시퀀스를 반환하는 메서드를 작성할 때, 메서드가 오직 Stream 파이프라인에서만 쓰인다면 마음놓고 Stream을 반환
      하지만
      • for-each를 사용하는 개발자와 Stream을 사용하는 개발자를 모두 배려하여 Stream과 Iterable을 동시에 제공할 수 있도록 하는 것이 좋음
      따라서
      • 원소 시퀀스를 반환하는 공개 API의 반환 타입에는 Collection이나 그 하위타입을 쓰는 것이 일반적


    • 어댑터 + 전용컬렉션 사용(멱집합 예제)
      • 멱집합 :  한 집합의 모든 부분집합을 원소로 하는 집합(n이 커질 수럭 기하급수적으로 커짐)
        •  (a, b, c)의 멱집합은 ((), (a), (b), (c), (a, b), (a, c), (b, c), (a, b, c))
        • 원소의 갯수가 n개일 때, 원소의 갯수는 2^n개

      반환하는 시퀀스의 크기가 메모리에 올려도 안전할 만큼 작다면 ArrayList나 HashSet 같은 표준 컬렉션 구현체를 반환하는게 최선일 수 있음.
      (하지만 단지 컬렉션을 반환한다는 이유로 덩치 큰 시퀀스를 메모리에 올려서는 안됨)

      코드 블럭
      languagejava
      themeMidnight
      firstline1
      title컬렉션 내의 시퀀스가 크면 전용 컬렉션을 구현하라반환할 시퀀스가 크지만 표현을 간결하게 할 수 있다면 전용 컬렉션 구현하라.
      linenumberstrue
      collapsetrue
      public class PowerSet {
          // Returns the power set of an input set as custom collection 
      	/* 입력 집합의 원소 public수가 static30을 final넘으면 <E>PowerSet.of가 Collection<Set<E>> of(Set<E> s) {
              List<E> src = new ArrayList<>(s);
              if (src.size() > 30)
                  throw new IllegalArgumentException("너무 많음 예외 : minimum 30 " + s);
      		/* 입력 집합의 원소 수가 30을 넘으면 PowerSet.of가 예외를 던진다.
      		(size() 메서드의 리턴타입은 int이기 때문에 최대길이는 2^31 - 1 또는 Integer.MAX_VALUE로 제한 되기 때문)
      		이는 Stream이나, Iterable이 아닌 Collection을 쓸 때의 단점을 보여준다.
      		(Stream이나 Iterable은 size에 대한 고민이 필요없기 때문)
      		*/예외를 던진다.
      			(size() 메서드의 리턴타입은 int이기 때문에 최대길이는 2^31 - 1 또는 Integer.MAX_VALUE로 제한 되기 때문)
      			이는 Stream이나, Iterable이 아닌 Collection을 쓸 때의 단점을 보여준다.
      			(Stream이나 Iterable은 size에 대한 고민이 필요없기 때문)
      			반환하는 시퀀스의 크기가 메모리에 올려도 안전할 만큼 작다면 ArrayList나 HashSet 같은 표준 컬렉션 구현체를 반환하는게 최선
      			(하지만 단지 컬렉션을 반환한다는 이유로 덩치 큰 시퀀스를 메모리에 올려서는 안됨) 
      			반환할 시퀀스가 크지만 표현을 간결하게 할 수 있다면 전용 컬렉션 구현*/
          public static final <E> Collection<Set<E>> of(Set<E> s) {
              List<E> src = new ArrayList<>(s);
              if (src.size() > 30)
                  throw new IllegalArgumentException("너무 많음 예외 : minimum 30 " + s);
      		
               return new AbstractList<Set<E>>() {
      			//size와 contains 등을 구현하기 어려운 경우, 스트림이나 Iterable을 반환하는 편이 낫다.
                  @Override public int size() {
                      return 1 << src.size(); // 2멱집합의 to크기는 the2를 power원래 srcSize집합의 원소 수만큼 거듭제곱한 것과 같음
                  }
      
      
                  @Override public boolean contains(Object o) {
                      return o instanceof Set && src.containsAll((Set)o);
                  }
      
                  @Override public Set<E> get(int index) {
                      Set<E> result = new HashSet<>();
                      for (int i = 0; index != 0; i++, index >>= 1)
                          if ((index & 1) == 1)
                              result.add(src.get(i));
                      return result;
                  }
              };
          }
      
          public static void main(String[] args) {
              Set s = new HashSet(Arrays.asList(args));
              System.out.println(PowerSet.of(s));
          }
      }
      
    • 예제2
      • (a, b, c)의 prefixes는 (a), (a, b), (a, b, c) 이다
      • (a, b, c)의 suffixes는 (c), (b, c), (a, b, c) 이다
      • Stream.concat 메서드는 반환되는 Stream에 빈 리스트를 추가하며, flatMap은 모든 Stream을 하나의 Stream으로 만든다.
코드 블럭
languagejava
themeMidnight
firstline1
title입력 리스트의 모든 부분 리스트를 Stream으로 반환리스트에 (연속적인) 부분리스트를 모두 반환하는 메서드
linenumberstrue
collapsetrue
public class SubLists {
    	//Stream.concat Returns메서드는 a반환되는 streamStream에 of all리스트를 the추가
sublists	// offlatMap은 its모든 inputStream을 list하나의 (PageStream으로 219)만든다
    public static <E> Stream<List<E>> of(List<E> list) {
        return Stream.concat(Stream.of(Collections.emptyList()),
                prefixes(list).flatMap(SubLists::suffixes));
    }


    private static <E> Stream<List<E>> prefixes(List<E> list) {
        return IntStream.rangeClosed(1, list.size())
                .mapToObj(end -> list.subList(0, end));
    }

    private static <E> Stream<List<E>> suffixes(List<E> list) {
        return IntStream.range(0, list.size())
                .mapToObj(start -> list.subList(start, list.size()));
    }

	/*(a, b, c)의 prefixes는 (a), (a, b), (a, b, c) 
	(a, b, c)의 suffixes는 (c), (b, c), (a, b, c).*/


	// 위와 같은 로직(for문 사용)
	for (int start = 0; start < src.size(); start++) {
   	 for (int end = start + 1; end <= src.size(); end++) {
        System.out.println(src.subList(start, end));
   	 }
	}


	// 위와 같은 로직(Stream 중첩) - 더 간결해 지지만 가독성이 떨어질 수 있다.
	public static <E> Stream<List<E>> of(List<E> list) {
	    return IntStream.range(0, list.size())
        .mapToObj(start -> 
                  IntStream.rangeClosed(start + 1, list.size())
                           .mapToObj(end -> list.subList(start, end)))
        .flatMap(x -> x);
	}


    public static void main(String[] args) {
        List<String> list = Arrays.asList(args);
        SubLists.of(list).forEach(System.out::println);
    }
}

...

  • 요약 
    • Stream이나 Iterable을 리턴하는 API에는 Stream -> Iterable, Iterable -> Stream으로 변환하기 위한 어댑터 메서드가 필요
      • 단, 어댑터는 클라이언트 코드를 어수선하게 만들고 더 느리다 (책에서는 2.3배정도 느리다함)
    • 원소 시퀀스를 반환하는 메서드를 작성할 때는 Stream, Iterator를 모두 지원할 수 있게 작성(되도록 Collection으로 하는게 좋음)
    • 원소의 갯수가 많다면, 멱집합의 예처럼 전용 컬렉션을 리턴하는 방법도 고민
    • 만약 나중에 Stream 인터페이스가 Iterable을 지원하도록 수정된다면, 그때는 안심하고 Stream을 반환하면 됨

...

  • 서론 : 주류 언어 중, 동시성 프로그래밍 측면에서는 항상 자바는 앞서왔음을 강조
    • 처음 릴리즈된 1996년부터 스레드, 동기화, wait/notify를 지원

    • (자바 5) 동시성 컬렉션인 java.util.concurrent 라이브러리와 실행자(Excutor) 프레임워크를 지원
    • (자바 7) 고성능 병렬 분해(parallel decom-position) 프레임워크인 fork-join 패키지를 추가
      (Fork-join pool에 대한 설명은 https://okky.kr/article/345720 참고)

  • 자바로 동시성 프로그램을 작성하기가 점점 쉬워지고 있지만, 이를 올바르고 빠르게 작성하는 일은 어려움
    • (자바8) 스트림은 parallel 메서드만 호출하면 자동으로 병렬 실행할 수 있는 스트림을 지원
    • 스트림 병렬화가 성능에 미치는 영향
      *메르센 소수(Mersenne number): 2의 거듭제곱에서 1이 모자란 숫자

      : 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767...


      *스트림을 이용한 처음 20개의 메르센 소수를 생성하는 프로그램
      test1 수행시간 : 8.2초  test2(parallel()을 호출) : 메르센 소수의 값이 프린트 되지 않았고, 강제로 중지 하기 전까지 계속 돌고 있었다.
      아무것도 안된 원인은 stream 라이브러리가 이 파이프라인을 병렬화 하는 방법을 찾아내지 못했기 때문이다.

      • 데이터 소스가 Stream.iterate인 경우
      • 중간 연산으로 limit()를 사용하는 경우 위 두 가지 경우에는 파이프라인 병렬화로 성능 향상을 기대하기 어렵다.
        뿐만 아니라 파이프라인 병렬화는 limit를 다룰 때 CPU 코어가 남는다면, 원소를 몇 개 더 처리한 후 제한된 개수 이후의 결과를 버려도 아무런 해가 없다고 가정한다.

      따라서 스트림 파이프라인을 마구잡이로 병렬화 해선 안된다. 성능이 오히려 더 나빠질 수 있다.

      코드 블럭
      languagejava
      themeRDark
      firstline1
      title파이프라인 병렬화가 불가능 한 경우 성능 개선이 되지 않는다.
      linenumberstrue
      collapsetrue
      public class ParallelMersennePrimes {
          public static void main(String[] args) {
              primes().map(p -> TWO.pow(p.intValueExact()).subtract(ONE))
                      .parallel()
                      .filter(mersenne -> mersenne.isProbablePrime(50))
                      .limit(20)
                      .forEach(System.out::println);
          }
      
      
          static Stream<BigInteger> primes() {
              return Stream.iterate(TWO, BigInteger::nextProbablePrime);
          }
      }
      
      
      public class ParallelPrimeCounting {
          // Prime-counting stream pipeline - parallel version (Page 225)
          static long pi(long n) {
              return LongStream.rangeClosed(2, n)
                      .parallel()
                      .mapToObj(BigInteger::valueOf)
                      .filter(i -> i.isProbablePrime(50))
                      .count();
      		/* pararell (무)31s / (유)9.2s -> 약 3.27배4배 빨라짐 */
          }
      
          public static void main(String[] args) {
              System.out.println(pi(10_000_000));
          }
      }

...

    • 스트림을 잘못 병렬화하면 (응답 불가를 포함해) 성능이 나빠질 뿐만 아니라 결과 자체가 잘못되거나 예상 못한 동작이 발생할 수 있다.(
      saftey failure : 결과가 잘못되거나 오동작하는 것은 안전 실패(safety failure) 이라 한다.)것. 안전실패
      안전 실패는 병렬화한 파이프라인이 사용하는 mappers, filters 혹은 프로그래머가 제공한 다른 함수 객체가 명시한대로 동작하지 않을 때 발생할 수 있다.발생 
    • Stream 명세는 함수 객체에 대한 규약 참조
      • Stream의 reduce 연산에 건네지는 accumulator(누적기)와 combiner(결합기) 함수는 반드시 결합법칙만족해야 한다.만족해 함
        • 결합 법칙 : (a op b) op c = a op (b op c))
      • 간섭받지 않아야 한다 (non-interfering) - 파이프라인이 수행되는 동안 데이터소스가 변경되지 않아야한다
      • 상태를 갖지 않아야 한다 (stateless) 
      • 위의 요구사항을 지키지 못하더라도 순차적으로 실행하면 올바른 결과를 얻을 수 있다.
      • 하지만 병렬로 수행하면 기대한 결과가 나오지 않을 수 있고, 실패할 수 있으니 주의해야 한다.주의

  • 스트림 병렬화는 오직 성능 최적화 수단임을 기억하라
    • 다른 최적화와 마찬가지로 변경 전후로 반드시 성능테스트를 진행하여 병렬화를 사용할 가치가 있는지 확인해야 한다.확인
    • 이상적으로는 운영 시스템과 같은 환경에서 테스트하는 것이 좋다.테스트
    • 보통은 병렬 스트림 파이프라인도 공통의 포크-조인 풀에서 수행되므로 (같은 스레드 풀을 사용)잘못된 파이프라인 하나가 다른 부분의 성능에까지 악영향을 줄 수 있음을 유념하자있음
    • 조건이 잘 갖춰지면 parallel 메서드 호출 하나로 거의 프로세서 코어 수에 비례하는 성능 향상을 볼 수 있다.향상
코드 블럭
languagejava
themeMidnight
firstline1
title스트림 파이프라인 병렬화가 효과적인 예
linenumberstrue
collapsetrue
static long pi(long n) {
    return LongStream.rangeClosed(2, n)
                     .mapToObj(BigInteger::valueOf)
                     .filter(i -> i.isProbablePrime(50))
                     .count();
}



static long pi(long n) {
    return LongStream.rangeClosed(2, n)
                     .parallel()
                     .mapToObj(BigInteger::valueOf)
                     .filter(i -> i.isProbablePrime(50))
                     .count();
}

...