Kotlin closure

Jungwook Park
Nov 4 · 5 min read

Close + Over

kotlin 1.3.50, java 1.8 기준으로 작성하였습니다.


java code 에서 Thread 를 사용하다보면 아래와 같은 컴파일러 오류를 볼 수 있다.

void testRun() {
int value = 0;
new Thread(() -> value++).start();
}
local variables referenced from an inner class must be final or effectively final

간단하게 말하면 JLS-15.27.2 때문이다.

Any local variable, formal parameter, or exception parameter used but not declared in a lambda expression must either be declared final or be effectively final (§4.12.4), or a compile-time error occurs where the use is attempted.

이 제약은 java synchronized 문제를 해결하기 위해 생겼다.

Synchronized 문제는 크게 두 가지로 인해 발생하는데, visibility problem 과 reordering 때문이다.

cpu instruction 구문 수행은 아래와 같은 형태로 수행된다.

fetch -> decode -> execution

  1. memory 에서 값을 가져온다
  2. cpu 에서 값을 해석한다.
  3. cpu 에서 명령을 수행한다.

cpu 와 memory 사이 통신은 비용이 발생하기 때문에, 현대의 cpu 는 수행한 명령의 값을 cpu 의 cache (L1등)에 저장한다.

multi core 환경에서 cpu cache 는 cpu 간의 상호 cache 값을 볼 수 없으며 서로 값을 볼 수 없는 visibility problem 이 발생한다.

즉, thread 1 과 thread 2 가 각각 memory 로부터 값을 읽어와 값을 변경하였더라도 그 값을 공유하지 않는 cache 에 저장한다면, visibility problem 이 발생하고 synchronized 문제가 발생한다.

성능 최적화를 위해 code 실행 순서를 compiler 등이 바꾸는 것을 의미한다. cpu instruction 은 고차 언어로 작성하는 source code 와 다르기 때문에 성능 최적화를 위해서는 reordering 이 불가피하다. 하지만 이로 인해 program 이 의도와 다르게 동작할 가능성이 발생한다.


이러한 문제를 해결하기 위해 JSR-133 이 제안되었고, JSR-133은 Synchronized 문제를 해결하고 Safety Guarantee 를 하기 위한 내용이다. 7장 Specification of the Java Memory Model 부분의 happens-before, Causality 두 가지 내용을 확인할 수 있다.

구문(statement) A 가 값을 쓴 경우 구문 B가 이를 볼 수 있음을 보장해야 한다. (visibility problem 해결 보장)

구문 A, B, C, D … 등이 서로 영향을 미칠 때 실제 각 구문간 상태에 영향을 비치는 그 세부의 구문 그래프를 확인하고 서로영향이 미칠 수 있는 부분들에 대해 인과 관계를 보장하도록 하는 것이다. (구문간의 영향 순서 보장)


위 코드는 visibility problem 을 발생시킬 가능성이 있는데, 그 이유는 final value 는 constructor 생성 전에 초기화 되지만, non-final value 는 그렇지 않기 때문이다. non-final value 는 visibility problem 을 유발할 가능성이 있다. 따라서 local variables referenced from an inner class must be final or effectively final compiler 오류가 발생하게 된다.


반면 동일한 kotlin 코드는 다음과 같은데

fun testRun() {
var value = 0
Thread { value++ }.start()
}

오류가 발생하지 않는다. kotlin 이 closure 를 지원하기 때문이다.

closure 는 function 과 environment context 를 같이 저장하는 단위이다.

위 코드에서 value 는 자유 변수(free variable) 라고 하며 { value ++} 는 자유 변수를 참조하고 있다. { value++ } 는 자기 자신과 outer scope 의 자유 변수인 value 를 묶어 (capture) 취급하게 되고, 자유 변수는 이 묶음에 대해 닫힌 상태가 된다. (close over)

{ value++ } 에 value 함께 function 과 environment context 가 하나처럼 취급되므로 오류가 발생하지 않는다.


kotlin bytecode 를 decompile 해보면 아래와 같다.

final IntRef value = new IntRef();
value.element = 0;
(new Thread((Runnable)(new Runnable() {
public final void run() {
int var10001 = value.element++;
}
}))).start();

final 로 선언한 IntRef 객체를 넘겨 내부적으로는 처리하고 있는 것을 알 수 있다.

Jungwook Park

Written by

kjcoop

kjcoop

경기광주 개발협동조합

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade