티스토리 뷰

● 도입

- Parallel Stream : 기존에 순차처리(Sequential) 하던 Stream을 병렬처리(Parallel) 하도록 만들어준다.

List<Integer> numbers = Arrays.asList(1, 2, 3);
Stream<Integer> parallelStream = numbers.parallelStream(); // 바로 병렬 Stream을 만들어준다.
Stream<Integer> parallelStream2 = numbers.stream().parallel(); // 기존의 Stream을 중간에 parallel로 바꾸는 것도 가능

[ Sequential(순차처리) vs Parallel(병렬처리) ]
- 여러개의 스레드를 이용하여 stream의 처리 과정을 병렬화(parallelize) = 여러개의 스레드를 이용하여 중간 처리들을 병렬로 처리한다.
중간 과정은 병렬 처리되지만 순서가 있는 Stream의 경우 종결 처리했을 때의 결과물이 기존의 순차적 처리와 일치하도록 종결 처리 과정에서 조정된다.

- 즉, 중간에 병렬 처리가 되어도 기존의 순서는 그대로 유지된다는 의미

ex) List로 collect 한다면 순서가 항상 올바르게 나온다는 것이다.

- 반면에 중간 처리의 과정은 뒤죽박죽이다.(여러 thread에서 실행되기 때문에), 마지막으로 모아주는 것만 순서대로 모아진다고 생각하면 된다.


[ 장단점 ]
장점
1. 굉장히 간단하게 병렬 처리를 사용할 수 있게 해준다.
2. 여러개의 스레드를 이용해 동시에 중간 처리를 하기 때문에 속도가 비약적으로 빨라질 수 있다.

단점
1. 항상 속도가 빨라지는 것은 아니다.
2. 공통으로 사용하는 리소스가 있을 경우 잘못된 결과가 나오거나 아예 오류가 날 수도 있다.(deadlock)
2-1. 이를 막기 위해 mutex, semaphore 등 병렬 처리 기술을 이용하면 오히려 순차 처리보다 느려질 수도 있다.

 

결론 : 작업을 할 때 Sequential(순차처리)과 Parallel(병렬처리) 중 어느게 더 적절한 지 고민을 한 후 사용해야한다.

 

 

● 실습

1. 검증되지 않은 유저를 골라내어 이메일을 보내자

- 이메일 발송 작업은 순서가 중요한 작업이 아니므로 바로 이럴때 병렬처리를 사용하면 좋다.
(메일 발송은 순서가 상관없기 때문)

package com.fastcampus.functionalprogramming.chapter8;

import java.util.Arrays;
import java.util.List;

import com.fastcampus.functionalprogramming.chapter8.model.User;
import com.fastcampus.functionalprogramming.chapter8.service.EmailService;

public class Chapter8Section10 {
	public static void main(String[] args) {
		User user1 = new User()
				.setId(101)
				.setName("Alice")
				.setVerified(true)
				.setEmailAddress("alice@fastcampus.co.kr");
	    User user2 = new User()
	    		.setId(102)
	    		.setName("Bob")
	    		.setVerified(false)
	    		.setEmailAddress("bob@fastcampus.co.kr");
	    User user3 = new User()
	    		.setId(103)
	    		.setName("Charlie")
	    		.setVerified(false)
	    		.setEmailAddress("charlie@fastcampus.co.kr");
	    User user4 = new User()
	            .setId(104)
	            .setName("David")
	            .setEmailAddress("david@fastcampus.co.kr")
	            .setVerified(true);
        User user5 = new User()
        		.setId(105)
        		.setName("Eve")
        		.setEmailAddress("eve@fastcampus.co.kr")
        		.setVerified(false);
        User user6 = new User()
        		.setId(106)
        		.setName("Frank")
        		.setEmailAddress("frank@fastcampus.co.kr")
        		.setVerified(false);
        
	    // TODO  : 검증되지 않은 유저를 골라내어 이메일을 보내자
        /*
         * - 순서가 상관이 없는 작업을 한다면 parallelStream을 사용할 수 있다.
         * - 이메일 발송 작업은 순서가 중요한 작업이 아니므로 바로 이럴때 병렬처리를 사용하면 좋다.
         */
        
        // 순차처리
        List<User> users = Arrays.asList(user1, user2, user3, user4, user5, user6);
        
        long startTime = System.currentTimeMillis();
        EmailService emailService = new EmailService();
	    users.stream()
    		.filter(user -> !user.isVerified())
    		.forEach(emailService::sendVerifyYourEmailEmail);	    
        long endTime = System.currentTimeMillis();
        System.out.println("Sequential(순차처리) : " + (endTime - startTime) + "ms");
        // 순차처리는 처리 결과도 users List에 담긴 순서대로 처리된다.
	    
	    
	    // 병렬처리
        List<User> users2 = Arrays.asList(user1, user2, user3, user4, user5, user6);
        long startTime2 = System.currentTimeMillis();
        EmailService emailService2 = new EmailService();
        users2.stream().parallel()
			.filter(user -> !user.isVerified())
			.forEach(emailService2::sendVerifyYourEmailEmail);	
        long endTime2 = System.currentTimeMillis();
        System.out.println("Parallel(병렬처리) : " + (endTime2 - startTime2) + "ms");
        // 병렬처리는 users List에 담긴 순서대로 처리되지 않았다.
        // 메일을 보내는 것은 순서가 상관없으니 병렬처리를 사용해도 문제없다.
	}
}

병렬처리의 작업 시간이 확실히 적게 든다.

- 순차처리는 메일 발송도 이름의 알파벳 순서대로 진행됐지만 병렬처리는 순서대로 진행되지 않았다.

 

2.

- 순서가 상관이 있는 작업을 한다면 parallelStream을 사용할 수 없다.

package com.fastcampus.functionalprogramming.chapter8;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import com.fastcampus.functionalprogramming.chapter8.model.User;
import com.fastcampus.functionalprogramming.chapter8.service.EmailService;

public class Chapter8Section10 {
	public static void main(String[] args) {
		User user1 = new User()
				.setId(101)
				.setName("Alice")
				.setVerified(true)
				.setEmailAddress("alice@fastcampus.co.kr");
	    User user2 = new User()
	    		.setId(102)
	    		.setName("Bob")
	    		.setVerified(false)
	    		.setEmailAddress("bob@fastcampus.co.kr");
	    User user3 = new User()
	    		.setId(103)
	    		.setName("Charlie")
	    		.setVerified(false)
	    		.setEmailAddress("charlie@fastcampus.co.kr");
	    User user4 = new User()
	            .setId(104)
	            .setName("David")
	            .setEmailAddress("david@fastcampus.co.kr")
	            .setVerified(true);
        User user5 = new User()
        		.setId(105)
        		.setName("Eve")
        		.setEmailAddress("eve@fastcampus.co.kr")
        		.setVerified(false);
        User user6 = new User()
        		.setId(106)
        		.setName("Frank")
        		.setEmailAddress("frank@fastcampus.co.kr")
        		.setVerified(false);
        
        // 순서가 상관이 있는 작업을 한다면 parallelStream을 사용할 수 없다.
        // TODO user의 이름을 대문자로 바꾼 후 (isVerified = true)로 설정하고 싶다.
        /*
         * 순서를 지켜야 하는 작업이다.
         * 1. user의 이름을 대문자로 바꾼 후
         * 2. (isVerified = true)로 설정하고 싶다.
         */
        List<User> users3 = Arrays.asList(user1, user2, user3, user4, user5, user6);
        
        // 아래의 작업은 users3 List에 담긴 순서와 상관 없이 실행될 때마다 순서가 뒤죽박죽으로 실행될 것이다.
        List<User> processedUsers = users3.parallelStream()
        		.map(user -> {
        			System.out.println("Capitalize user name for user " + user.getId());
        			user.setName(user.getName().toUpperCase());
        			return user;
        		})
        		.map(user -> {
        			System.out.println("Set 'isVerified' to true for user " + user.getId());
        			user.setVerified(true);
        			return user;
        		})
        		.collect(Collectors.toList());
       
       System.out.println("==================== 최종 결과물은 순서대로 출력 O ====================");
       
       // 최종 결과물을 출력하는 아래의 코드는 users3 List에 담긴 순서대로 출력될 것이다.
       for (User user : processedUsers) {
    	   System.out.println("Name : " + user.getName() + " / isVerified : " + user.isVerified());
       }
	}
}

 

- 최종 결과물은 순서대로 출력되지만 중간 처리 과정은 매 실행 때마다 순서가 다르다는 것을 확인 할 수 있다.

 

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
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 31
글 보관함