lazy val에 대해 잘 설명된 블로그 글이다.
https://blog.codecentric.de/en/2016/02/lazy-vals-scala-look-hood/
스칼라의 LazyCell 클래스에는 lazy val이 있다.
final class LazyCell { lazy val value: Int = 42 }
자바로 디컴파일 해보면 아래와 같이 변환된다고 블로그 글에 나와 있다.
final class LazyCell { @volatile var bitmap_0: Boolean = false // (1) var value_0: Int = _ // (2) private def value_lzycompute(): Int = { this.synchronized { // (3) if (!bitmap_0) { // (4) value_0 = 42 // (5) bitmap_0 = true } } value_0 } def value = if (bitmap_0) value_0 else value_lzycompute() // (6) }
scala 2.12로 컴파일하고 실제로 jad 로 디컴파일하면 다음과 같다. 거의 동일하다.
내부적으로 volatile과 synchronized를 사용한다. 즉 multiple thread에서 동기화가 보장되도록 되어 있다! 예제2에서 설명하고 있다.
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: LazyCell.scala
public final class LazyCell
{
private int value$lzycompute()
{
synchronized(this)
{
if(!bitmap$0)
{
value = 42;
bitmap$0 = true;
}
}
return value;
}
public int value()
{
return bitmap$0 ? value : value$lzycompute();
}
public LazyCell()
{
}
private int value;
private volatile boolean bitmap$0;
}
예제 1이다. 참조 블로그의 1번째 예제를 repl에서 실행해 본다.
scala> :paste
// Entering paste mode (ctrl-D to finish)
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import scala.concurrent.duration._
def fib(n: Int): Int = n match {
case x if x < 0 =>
throw new IllegalArgumentException(
"Only positive numbers allowed")
case 0 | 1 => 1
case _ => fib(n-2) + fib(n-1)
}
object ValStore {
lazy val fortyFive = fib(45) // (1)
lazy val fortySix = fib(46) // (2)
}
object Scenario1 {
def run = {
val result = Future.sequence(Seq( // (3)
Future {
println(ValStore.fortyFive)
println("done (45)")
},
Future {
println(ValStore.fortySix)
println("done (46)")
}
))
Await.result(result, 1.minute)
}
}
// Exiting paste mode, now interpreting.
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import scala.concurrent.duration._
fib: (n: Int)Int
defined object ValStore
defined object Scenario1
scala> Scenario1.run
1836311903
done (45)
-1323752223
done (46)
res4: Seq[Unit] = List((), ())
처음 실행할 때는 속도가 걸리지만, 다음 번 실행할 때는 무척 빠르다. lazy val의 특성이 있다.
scala> Scenario1.run
-1323752223
done (46)
1836311903
done (45)
res5: Seq[Unit] = List((), ())
scala> Scenario1.run
1836311903
done (45)
-1323752223
done (46)
res6: Seq[Unit] = List((), ())
2번째 예제는 lazy val의 내부 synchronized를 이용해 deal lock을 유발시키는 코드이다. 여러 쓰레드를 사용하면서 lazy val을 잘 못 쓴다면 dead lock이 발생할 수 있다.
scala> :paste
// Entering paste mode (ctrl-D to finish)
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import scala.concurrent.duration._
object A {
lazy val base = 42
lazy val start = B.step
}
object B {
lazy val step = A.base
}
object Scenario2 {
def run = {
val result = Future.sequence(Seq(
Future { A.start }, // (1)
Future { B.step } // (2)
))
Await.result(result, 1.minute)
}
}
3번째 예제이다. 참조 블로그를 보면 deadlock 예제로 되어 있다.
scala> :paste
// Entering paste mode (ctrl-D to finish)
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import scala.concurrent.duration._
trait Compute {
def compute: Future[Int] =
Future(this.synchronized { 21 + 21 }) // (1)
}
object Scenario3 extends Compute {
def run: Unit = {
lazy val someVal: Int =
Await.result(compute, 1.minute) // (2)
println(someVal)
}
}
실제로 실행해 보면 deadlock은 발생되지 않는다.
scala> Scenario3.run
42
lazy val에 synchronized가 된다면 인스턴스는 분명 deadlock 상황에 빠져야 한다. 그러나 컴파일러가 똑똑해져서 문제가 발생하지는 않는다.
lazy val 다음 단계는 http://docs.scala-lang.org/sips/pending/improved-lazy-val-initialization.html에 있다.
'scala' 카테고리의 다른 글
scala REPL에서 main 클래스/함수 실행시키기 (0) | 2017.11.17 |
---|---|
스칼라의 접근 한정자 (0) | 2017.11.07 |
[sbt] hbase 연동시 만난 library 의존성 관련 exception 처리하기 (0) | 2017.11.03 |
[play2] globalsetting (0) | 2017.11.02 |
play2에서 apache phoenix 드라이버 사용 이슈. (0) | 2017.10.31 |