람다에서 사용하는 외부 변수가 final 이어야 하는 이유

@MinSang · August 27, 2024 · 3 min read

람다는 메소드 내에서 선언이 되는데, 이 메소드에 있던 지역변수, 파라미터변수를 람다도 역시 사용이 가능합니다.

또한 람다는 다른 스레드에서 사용할 수 있기 때문에 해당 지역변수나 파라미터 변수에 접근이 가능하기 때문에 동시성 문제가 일어날 수 있어서 final로 선언해야합니다. (자바 8부터는 적지않아도 final로 자동 생성)

근데 의문이 드는게 지역변수는 스레드의 스택에서 생성이 될텐데 어떻게 다른 스레드가 이 지역변수에 접근이 가능한가 일 것입니다.

그건 바로, 람다의 특징인 변수 캡처(Variable Capture) 에 있습니다.

변수 캡처

Lambda 표현식에서 외부 변수를 사용하는 것을 '변수 캡처'라고 합니다.

이는 두 가지 방식으로 이루어집니다:

  • 값 캡처 (Value Capture): 기본 타입(primitive types)의 경우

    • 기본 자료형(primitive types)이나 불변 객체의 경우 사용됩니다.
    • 변수의 값이 복사되어 람다 내부로 전달됩니다.
  • 참조 캡처 (Reference Capture): 객체의 경우

    • 객체에 대한 참조가 캡처됩니다.

람다가 다른 스레드에서 실행될 수 있기 때문에, 변수의 값이 변경되면 동시성 문제가 발생할 수 있습니다. final 또는 effectively final 변수만 허용함으로써 이런 문제를 방지합니다.

public class LambdaExample {
    public static void main(String[] args) {
        int value = 10;  // Effectively final
        String text = "Hello";  // Effectively final
        
        Runnable r = () -> {
            System.out.println(value);  // 값 캡처
            System.out.println(text);   // 참조 캡처
            // value++; // 컴파일 에러
        };
        
        new Thread(r).start();  // 새로운 스레드에서 실행
        // value = 20; // 이 줄의 주석을 해제하면 Lambda에서 컴파일 에러 발생
    }
}

정리

림다는 외부 변수를 사용하게 되면 다른 스레드에서도 해당 변수에 접근이 가능하도록 해야하기 때문에 캡처링 기능을 통해 값 캡처 또는 참조 캡처로 변수를 복사하여 힙 영역에 위치시켜야 합니다. (외부 변수는 지역변수나 파라미터 변수이기 떄문에 스택영역에 있어서 다른 스레드가 접근 불가능하기 때문)

그리고 이 람다를 사용하는 다른 스레드가 복사된 지역 변수를 수정하는 동시성 문제나 일관성 문제가 발생할 수 있기 때문에 반드시 final을 보장해야 합니다.

@MinSang
지식과 경험을 기록하는 TIL 저장소