Stream.parallel() 을 사용하여 List 에 값을 추가하고, List를 반환하여 추가 작업을 하려는데 문제가 발생했습니다.

대략 아래와 같은 상황으로 List에 값을 추가하고 List를 그대로 반환하려고 했습니다.

@Test
public void List_는_멀티스레드에서_안전하지_않다() {
   List<Integer> list = new ArrayList<>();
   IntStream stream = IntStream.rangeClosed(0, 499);
   stream.parallel().forEach(i -> {
      list.add(i);
   });

   assertThat(list.size()).isEqualTo(500);
}

 

분명 parallel() 은 병렬 처리를 안전하게 쉽게 도와주는 것으로 알고

List 에는 500개의 요소가 있을 것으로 생각했지만, 매번 테스트 결과가 달랐습니다.

500개가 나올때도 있고, 아래처럼 495, 496개가 나올 때도 있었습니다.

 

이러한 문제는 병렬 처리 과정에서 여러 스레드에서 하나의 ArrayList 에 접근했기 때문입니다.

ArrayList 는 스레드 세이프 하지 않습니다.

 

해결방법은 CopyOnWriteArrayList와 SynchronizedList를 사용하는 방법이 있습니다.

CopyOnWriteArrayList 는 배열의 값을 세팅하고 추가하는 과정에 Lock 을 걸기 때문에 스레드 안전하다고 합니다.

SynchronizedList 는 자바의 synchronized 키워드를 사용하여 스레드 세이프하게 구현되어 있습니다.

 

 

그리고 비슷한 상황에서 stream의 collect를 사용할 수 있습니다.

아래는 List의 값들을 2배 씩하여 새로운 List에 추가하는 과정입니다.

당연하게도 ArrayList이기 때문에 테스트는 실패합니다.

@Test
public void List_는_멀티스레드에서_안전하지_않다() {
   List<Integer> tmp = new ArrayList<>();
   for(int i = 0 ; i <= 499 ; i++ ) {
      tmp.add(i);
   }

   List<Integer> list = new ArrayList<>();
   tmp.parallelStream().map(num -> num * 2 ).forEach(list::add);

   assertThat(list.size()).isEqualTo(500);
}

 

 

필터링을 거치고 collect를 사용해 새로운 List를 반환합니다

collect 는 cpu가 포크한 값들을 안전하게 모아 반환합니다.

@Test
public void List_는_멀티스레드에서_안전하지_않다() {
   List<Integer> tmp = new ArrayList<>();
   for(int i = 0 ; i <= 499 ; i++ ) {
      tmp.add(i);
   }

   List<Integer> list = tmp.parallelStream().map(num -> num * 2 ).collect(Collectors.toList());

   assertThat(list.size()).isEqualTo(500);
}

 

 

 

[참고]

모던자바인액션

https://mag1c.tistory.com/368

https://code0xff.tistory.com/182

@CotrollerAdvice 가 적용된 클래스는 지정한 범위의 컨트롤러에 공통으로 사용될 설정을 지정할 수 있습니다

컨트롤러에 공통으로 사용될 설정은 익셉션 처리, Model 에 속성 넣기 등 다양하게 가능합니다

 

 

1
2
3
4
5
6
7
8
9
10
11
@ControllerAdvice("abc")
public class CommonController {
 
    @GetMapping("/accounts/{id}")
    @ModelAttribute("myAccount")
    public Account handle() {
    // ...
    return account;
    }
 
}
cs

@ModelAttribute 가 위와같이 메소드 레벨에 있다면 @RequestMapping(여기서는 GetMapping) 메소드의 반환 값이 모델 속성이라고 볼 수 있습니다.

즉 Model.addAttribute() 를 사용한 것과 동일합니다.

메소드 인자에 사용한 것처럼 특별한 이름을 지정하고 싶으면 @ModelAttribute("name") 이렇게 사용이 가능합니다

 

그리고 중요한 점은 @ModelAttribute 애노테이션은 @RequestMapping 메소드 호출되기 전에 작동합니다

즉 abc 패키지에 포함된 컨트롤러가 아래와 같다면

abc 패키지에 있는 컨트롤러

"/" 요청을 보내면 home() 메서드가 실행되기 전에 @CotrollerAdvice 클래스 내부의 @ModelAttribute메서드가 실행됩니다

그리고 Model에 직접 지정한 "myAccount" 라는 이름으로 Account의 데이터를 타임리프,JSP와 같은 View에서 사용이 가능합니다

 

 

참고 출처

1. https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-modelattrib-methods

 

Web on Servlet Stack

Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, "Spring Web MVC," comes from the name of its source module (spring-webmvc), but it is more commonl

docs.spring.io

(공식 문서를 해석하여 잘못된 번역이 있을 수 있습니다.)

naver stmp 를 사용하여 이메일 인증 사용중 아래 오류 발생

 the sender address is unauthorized nsmtp

 

혹시나 yml에 설정한 네이버 아이디, 비밀번호를 가져오지 못했나? 싶어서 디버깅해보니

username , password 모두 잘 드간 것을 확인하였다.

그러다 검색을 통해 mimeMessageHelper에 메일을 보내는 사람이 누구인지도 설정해줘야 한다는 것을 보았다.
아래처럼 setFrom 메서드에 보내는 사람의 이메일 String 값을 넣었다 > ex. "아이디@naver.com"

구글 설정을 할 때는 from 세팅을 안하면 기본값으로 yml에서 설정한 username이 설정되는 것으로 알고 있었는데,

네이버 smtp를 사용할 때는 setFrom 설정을 해줘야 권한 통과가 되는 것 같다.

 

+ Recent posts