본문 바로가기
프로그래밍/JAVA 내용정리

[Java] Atomic Integer에 대하여.

by 노잼인간이라불립니다 2022. 8. 28.

스프링 자바 인 액션을 공부하는 과정에서 테스트 코드를 작성해 검증해야할 로직을 작성하게 되었다.

 

*** 문제

그 과정에서 스트림안에 람다식을 넣어 각 element를 순회하여 이전의 리스트와 값을 비교하는 로직이었는데, index++코드가 컴파일 오류가 나 실행할 수 없는 문제가 발생하였다.

 

int index = 0;
dishNames.parallelStream().forEach(dishname -> {
            Assertions.assertThat(menu.get(index++).getName()).isEqualTo(dishname);   //dishNames를 순회하면서 이름을
        });

컴파일 오류가 발생한 코드이다.

 

*** 문제 인식 및 해결방법 모색

처음에는 스트림과 관련된 오류인 줄 알았지만, 알고보니 람다식 안에 서는 final 변수만 사용가능 하기 때문에 발생한 오류였다.

 

 

***해결방법

람다식 안에서 index++ 와 같은 역할을 하는 변수를 사용하기 위해서는 2가지 방법이 있었다.

 

1. Atomic Integer 타입을 쓰는것.

 

2. array를 선언해 array안의 값을 계속 변경해주면서 사용하는 방법.

 

나는 두가지 방법을 다 시도해보았다.

 

첫 번째 방법인 Atomic Integer를 사용한 코드이다.

 

    @DisplayName("For-each를 사용하는 외부 반복")
    @Test
    public void useForeach() {
        List<String> dishNames = new ArrayList<>(); // 요리의 이름들을 담을 리스트
        for (Dish dish : menu) {
            dishNames.add(dish.getName());      // 요리의 이름들을 dishNames 리스트에 담음.
        }
        AtomicInteger index = new AtomicInteger();
        dishNames.stream().forEach(
                dishname -> Assertions.assertThat(menu.get(index.getAndIncrement()).getName()).isEqualTo(dishname)   //dishNames를 순회하면서 이름을 출력
        );

    }

 Atomic Integer는 멀티쓰레드 환경에서 Thread Safe 한 Wrapper 타입이라고 한다.

 

참고 : 자바 동시성 문제를 해결하는 3가지 방법.

  • volatile : Thread1에서 write하고, Thread2에서 read 하는 경우에만 동시성이 보장됩니다. 2개의 쓰레드에서 모두 write 하면 문제 발생.
  • synchronized :안전하게 동시성을 보장 가능. 비용 높음.
  • Atomic :  CAS (Compare And Swap)를 이용한 동시성을 보장. 여러 쓰레드에서 데이터를 write해도 Thread safe.

CAS(Compare And Swap) : thread 별 캐시값과 메인 메모리의 값이 같은 경우에 값을 변경. 말그대로 비교해서 같으면 변경 아니면 변경하지 않음.

 

두 번째 방법인 배열을 선언해 그 안의 데이터를 Increment 한 코드이다.

 @DisplayName("Iterator 객체를 사용하는 외부 반복")
    @Test
    public void useIterator() {
        List<String> dishNames = new ArrayList<>();     //For-each를 사용하는 외부 반복과 동일
        Iterator<Dish> iterator = menu.iterator();  //menu로 부터 iterator를 가져옴.

        while (iterator.hasNext()) {
            Dish dish = iterator.next();
            dishNames.add(dish.getName());          //iterator를 이용해 dish의 이름을 names리스트에 담음.
        }
        dishNames.stream().forEach(System.out::println);        //dish의 이름을 출력 (For-each를 통한 외부반복과 결과가 같음.)

//        AtomicInteger index = new AtomicInteger();
        final int[] index = {0};
        dishNames.stream().forEach(dishname -> {
            Assertions.assertThat(menu.get(index[0]++).getName()).isEqualTo(dishname);
            System.out.println(dishname);//dishNames를 순회하면서 이름을 출력
        });
    }

항상 람다 내부에선 final변수만 사용해야 한다고 알고 있어서 이렇게 값이 변할 수 있는 변수들을 사용하는 방법에 대해서 생각했는데, 테스트 코드 검증 시 스트림을 사용하면서 의도치 않게 이런 컴파일 오류 발생으로 인해 새로운 방법을 또 하나 알게 되었다.