DevJong12

Java의 람다식과 변수의 관계 본문

Java,Spring/Java

Java의 람다식과 변수의 관계

Jong12 2022. 8. 22. 02:21
728x90

개요

이번에 프로젝트로 소스를 지속해서 작성을 하면서 재밌어 보이는? 정확히는 내가 제대로 Stream에 대한 이해를 하지 못해 발생한 Issue를 알아보고자 한다.

 

많은 이슈가 있지만 해당 이슈를 따로 포스팅 하는 이유는 프로젝트를 진행하면서 어? 왜이러지? 하는 호기심과 Stream을  앞으로도 많이 써야 하는 것중 하나라 생각하나 , 너무 모르고 쓴것같아 작성하게 되었다.

 


이슈내용

람다식을 사용하면서 지역변수를 사용하려고 했으나 지역변수를 사용할 수 없는 문제였다.

 

내가 작성하려 했던 코드와 유사한 방식의 코드를 아래에 구현해봤다.

  public static void main(String[] args) {
    LamdaThread a = new LamdaThread();
    a.testStream();
  }

  public void testStream() {
    List<String> testList = new ArrayList<>();

    testList.add("a");
    testList.add("b");
    testList.add("c");
    testList.add("d");
    testList.add("e");
    testList.add("f");

    int cnt = 0;

    testList.stream().forEach(str -> cnt++);
    System.out.println(cnt);
  }

 

위의 소스에는 문제점이 하나 있다.

IDE에서 해당 소스를 작성해보면 forEach()안에서 cnt++부분을 에러사항으로 검출하게된다.

 

java: local variables referenced from a lambda expression must be final or effectively final

실제로 실행을 하게되면 위와 오류 메세지를 검출한다.

 

 

※ 람다만 발생하는게 아닌 InnerClass역시 같은 문제가 발생된다.


발생이유?

https://stackoverflow.com/questions/47998733/why-is-the-variable-used-in-lambda-expression-must-be-final-or-effectively-fina

 

Why is the "Variable used in Lambda expression must be final or effectively final" warning ignored for instance variables

I'm studying java 8 streams and the only problem I have is understanding lambdas is why the effectively final warning in lambdas is ignored for instance(and static) variables. I can't seem to find ...

stackoverflow.com

위의 글에서 답을 알수가 있었다.

You may be asking yourself why local variables have these restrictions. First, there’s a key difference in how instance and local variables are implemented behind the scenes. Instance variables are stored on the heap, whereas local variables live on the stack. If a lambda could access the local variable directly and the lambda were used in a thread, then the thread using the lambda could try to access the variable after the thread that allocated the variable had deallocated it. Hence, Java implements access to a free local variable as access to a copy of it rather than access to the original variable.
This makes no difference if the local variable is assigned to only once hence the restriction. Second, this restriction also discourages typical imperative programming patterns that mutate an outer variable.

이런 답변을 위의 글에서 볼수가 있었는데, 해당 글을 보고 IDE의 해결방은 AutomicInteger를 사용하라는 방안을 주는걸 보고 아래의 내용을 생각하게되었다.

 

먼저, 변수가 선언되는 메모리의 위치와 스레드의 메모리 공유 영역, AutomicInteger를 알아봐야 하지 않을까 생각을 해보게 되었다.

1. 변수의 메모리내에서의 보관되는 위치.

    a. 로컬변수는 스택에 저장된다.

    b. 인스턴스 변수는 Heap영역에 저장이 된다.

    c. JDK가 1.8 미만이라면 Static은 permanent영역의 static object에, 1.8이후라면 Heap영역에 저장이된다.

 

2. 스레드의 메모리 공유영역

    스레드는 멀티프로세스처럼 각자의 메모리 영역을 공유하지 않는다.

    '스택영역' 만 각자 소지를 하며, 메소드영역, Heap영역에 대하여 스레드간에 공유를 하게된다.

 

3. AutomicInteger?

    https://codechacha.com/ko/java-atomic-integer/ 다음의 페이지에서 설명을 가져왔다.

AtomicInteger 는  int자료형을 갖고 있는 wrapping 클래스 입니다. AtomicInteger 클래스는 멀티쓰레드 환경에서 동시성을 보장합니다.

 

위의 3개의 문제를 통하여 Thread라는 점을 알 수 있게 된다.

람다식, InnerClass는 다른 스레드로 인정이 된다고 생각이 되며, 다른 스택영역을 사용하다 보니, 로컬변수에 대하여 참조를 하지 못하게 되는 문제가 Issue가 된 코드를 사용하지 못하는 이유가 아닐까 싶다


해결 방안

메모리에서 서로 해당 값을 공유 할 수 있도록 하면 되지 않을까? 라는 생각을 하며, 크게 2가지 해결방안이 있을 듯 하다.

 

1. 인스턴스변수 혹은 클래스변수(static)로 선언한다.

  int cnt;

  public void testStream() {
    List<String> testList = new ArrayList<>();
    cnt = 0;

    testList.stream().forEach(str -> {
      cnt++;
      System.out.println(cnt);
    });
  }

2. IDE가 제공한 대로 AtomicInteger를 사용

AtomicInteger cnt = new AtomicInteger();

testList.stream().forEach(str -> {
  cnt.getAndIncrement();
  System.out.println(cnt.get());
});

추가적인 문제

1번문제의 추가적은 사항들에 대해서 기록을 해보고자 한다.

IDE에서 왜 AtomicInteger를 추천했을지를 생각해보니, AtomicInteger를 봤을 때 멀티스레드환경에서 동시성을 보장한다고 기록이 되어있다.

 

아마 인스턴스변수, 클래스 변수를 사용할때 해당값의 변경이 가능한 경우라면, 멀티스레드환경에서 해당 값의 보존, 즉 동기화 문제가 있을 것이라 생각이 된다.

 

추후 람다식 소스를 작성 할 때 변수의 선언위치를 한번쯤 다시 생각을 해봐야 할 것 같다.

728x90
Comments