람다는 메소드 내에서 선언이 되는데, 이 메소드에 있던 지역변수, 파라미터변수를 람다도 역시 사용이 가능합니다.
또한 람다는 다른 스레드에서 사용할 수 있기 때문에 해당 지역변수나 파라미터 변수에 접근이 가능하기 때문에 동시성 문제가 일어날 수 있어서 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을 보장해야 합니다.