Effective Kotlin - 가변 프로퍼티에서의 쓰레드 관련 문제

JINHO LEE·2024년 7월 1일
0

아이템 11: 가독성을 목표로 설계하라에서 나온 내용

가변 프로퍼티란? (mutable property)

값이 변경될 수 있는 프로퍼티를 의미한다.
코틀린에서는 var 키워드를 사용하여 가변 프로퍼티를 선언할 수 있다.
반면, 값이 한 번 설정되면 변경할 수 없는 프로퍼티는 불변 프로퍼티(immutable property)라고 하며, 이는 val 키워드를 사용하여 선언한다.

예시

가변 프로퍼티 (Mutable Property)
가변 프로퍼티는 선언된 후에 값을 변경할 수 있다.

var name: String = "Alice"
name = "Bob"  // name의 값을 변경할 수 있음

불변 프로퍼티 (Immutable Property)
불변 프로퍼티는 한 번 값을 할당하면 변경할 수 없다.

val name: String = "Alice"
// name = "Bob"  // 오류 발생: val 프로퍼티는 값을 변경할 수 없음

가변 프로퍼티의 특성

  1. 값 변경 가능: 가변 프로퍼티는 선언된 후 언제든지 새로운 값으로 변경할 수 있다.
  2. 쓰레드 안전성 문제: 여러 쓰레드가 동시에 같은 가변 프로퍼티에 접근하여 값을 변경할 때 동기화 문제가 발생할 수 있다. 이러한 이유로 코틀린에서는 스마트 캐스팅을 적용할 때 가변 프로퍼티를 신뢰하지 않는다.

스마트 캐스팅

코틀린의 스마트 캐스팅은 변수가 특정 조건을 만족하는지 검사한 후, 해당 조건 내에서 그 변수를 자동으로 캐스팅해주는 기능이다.
예를 들어, nullable 변수를 null 검사한 후, 그 변수를 non-null 타입으로 자동 캐스팅할 수 있다.

var userName: String? = null

fun printUserName() {
    if (userName != null) {
        // 이 시점에서 다른 쓰레드가 userName을 변경할 수 있기 때문에 스마트 캐스팅이 불가능함
        println(userName.length)  // 컴파일 오류 발생
    }
}

가변 프로퍼티의 쓰레드 안전성

가변 프로퍼티에서 여러 쓰레드가 동시에 같은 변수를 변경하려고 할 때 발생하는 문제가 발생한다.
코틀린의 스마트 캐스팅(smart casting)은 변수가 변경되지 않는다고 확신할 수 있을 때만 동작하기 때문에 가변 프로퍼티에는 적용되지 않는다.

아래 코드는 nullable 가변 프로퍼티가 있고, null이 아닐 때만 작업을 수행하는 예제이다.

kotlin
코드 복사
var userName: String? = null

fun processUserName() {
    if (userName != null) {
        // 다른 쓰레드가 이 시점에서 userName을 변경할 수 있음
        println(userName!!.length)  // 강제 캐스팅 사용
    }
}

fun main() {
    val thread1 = Thread {
        userName = "Alice"
        processUserName()
    }

    val thread2 = Thread {
        userName = null
    }

    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()
}

위 코드에서 thread1은 userName을 "Alice"로 설정한 후 processUserName을 호출한다.
동시에 thread2는 userName을 null로 설정하게 된다.
이 경우, processUserName 함수 내에서 userName이 null이 아닌지 확인한 후 userName!!.length를 호출하는 동안, 다른 쓰레드가 userName을 null로 변경할 수 있다.
이는 NullPointerException을 발생시킬 수 있다.

그리고 userName은 가변 프로퍼티이기 때문에 코틀린은 다른 쓰레드가 이 값을 변경할 수 있다고 판단한다. 따라서 스마트 캐스팅이 불가능하다.

let 함수와 가변 프로퍼티

let 함수를 사용하면 가변 프로퍼티의 null 안전성을 보다 쉽게 관리할 수 있다.

var userName: String? = null

fun printUserName() {
    userName?.let {
        println(it.length)  // 안전하게 userName을 사용할 수 있음
    }
}

여기서 userName?.let 구문은 userName이 null이 아닌 경우에만 let 블록 내의 코드를 실행한다.
it는 non-null 타입의 userName을 가리키며, 이 스코프 내에서는 userName이 변경되지 않는다는 보장이 있으므로 안전하게 it.length를 호출할 수 있게 된다.

0개의 댓글