synchronized() 및 withLock()을 사용하여 Kotlin에서 공유 리소스 잠금

David Mbochi Njonge 2023년6월20일
  1. Kotlin 프로젝트 생성
  2. 작동 중인 스레드 간섭
  3. Kotlin에서 synchronized() 함수를 사용하여 공유 리소스 잠금
  4. Kotlin에서 withLock() 함수를 사용하여 공유 리소스 잠금
  5. 결론
synchronized() 및 withLock()을 사용하여 Kotlin에서 공유 리소스 잠금

다중 스레드 애플리케이션으로 작업할 때 일관성 없는 데이터를 방지하기 위해 공유 리소스에 대한 액세스를 관리해야 합니다. 동기화는 애플리케이션에서 공유 리소스에 대한 액세스를 제어하려고 할 때 일반적으로 사용하는 접근 방식입니다.

잠금을 사용하여 동기화를 실현하여 현재 실행 중인 스레드가 실행을 완료할 때까지 스레드가 공유 리소스에 액세스하지 못하도록 합니다. 스레드는 공유 리소스에 액세스하고 완료되면 해제하기 전에 먼저 객체의 잠금을 획득해야 합니다.

잠금을 생성하는 방법에는 여러 가지가 있으며, 이 튜토리얼에서는 공유 리소스에 대한 액세스를 제어하기 위해 Kotlin에서 잠금을 만드는 방법을 알아봅니다.

Kotlin 프로젝트 생성

IntelliJ 개발 환경을 열고 파일 > 새로 만들기 > 프로젝트를 선택합니다. 열리는 창에서 프로젝트 이름을 kotlin-withlock으로 입력하고 언어 섹션에서 Kotlin을 선택한 다음 빌드 시스템으로 Intellij를 선택합니다. 마지막으로 만들기 버튼을 눌러 프로젝트를 생성합니다.

애플리케이션이 생성되면 src/main/kotlin 폴더 아래 Main.kt라는 파일을 생성합니다. 이 파일을 사용하여 이 자습서의 모든 코드 예제를 구현합니다.

작동 중인 스레드 간섭

스레드 간섭은 공유 리소스에 액세스하는 방법을 제어하지 않고 다중 스레드 응용 프로그램에서 발생합니다. 다음 코드는 다중 스레드 응용 프로그램에서 스레드 간섭이 발생하는 방식을 보여줍니다.

Main.kt 파일에 다음 코드를 복사하여 붙여넣습니다.

class Calculator {
    fun addIntArray() {
        var initialValue = 0;
        for (number in 1..5) {
            println(
                Thread.currentThread().name
                        + " ---> " + initialValue + " + "
                        + number + " = "
                    .plus(String.format("%d", (initialValue + number)))
            );
            initialValue += number;

            Thread.sleep(500);
        }
    }
}

fun main() {
    val calculator = Calculator();

    Thread {
        calculator.addIntArray();
    }.start();

    Thread {
        calculator.addIntArray();
    }.start();
}

이 코드에서는 Calculator라는 클래스 내에 addIntArray()라는 메서드를 만들었습니다. 이 메서드는 우리 코드의 공유 리소스이며 여러 스레드를 생성할 것이므로 현재 실행 중인 스레드를 콘솔에 기록할 수 있는 println() 메서드를 추가했습니다.

sleep() 메서드가 호출되면 현재 실행 중인 스레드가 500밀리초 동안 일시 중지되고 다른 스레드가 실행되기 시작합니다. 즉, 스레드가 동시에 메서드에 액세스하여 데이터 불일치가 발생할 수 있습니다.

main() 메소드에서 우리는 두 개의 스레드를 생성하고 start() 메소드를 호출하여 생성한 계산기 객체의 addIntArray() 메소드 실행을 시작했습니다.

이 코드를 실행하고 아래와 같이 스레드가 어떻게 인터리브되는지 확인합니다.

Thread-0 ---> 0 + 1 = 1
Thread-1 ---> 0 + 1 = 1
Thread-0 ---> 1 + 2 = 3
Thread-1 ---> 1 + 2 = 3
Thread-1 ---> 3 + 3 = 6
Thread-0 ---> 3 + 3 = 6
Thread-1 ---> 6 + 4 = 10
Thread-0 ---> 6 + 4 = 10
Thread-0 ---> 10 + 5 = 15
Thread-1 ---> 10 + 5 = 15

Kotlin에서 synchronized() 함수를 사용하여 공유 리소스 잠금

이 예제에서는 이전 섹션에서 구현한 addIntArray() 메서드를 사용하여 액세스 관리 방법을 보여줍니다. 이전 예제에서 생성한 main() 메서드를 주석 처리하고 주석 뒤에 다음 코드를 복사하여 Main.kt 파일에 붙여넣습니다.

val calculator = Calculator();

fun sumUsingSynchronization(): Unit = synchronized(calculator) {
    calculator.addIntArray();
}

fun main() {

    Thread {
       sumUsingSynchronization();
    }.start();

    Thread {
        sumUsingSynchronization()
    }.start();
}

이 자습서의 주요 목표는 공유 리소스에 대한 스레드의 배타적 액세스를 보장하기 위해 잠금이 생성되는 방법을 배우는 것입니다. 기본적으로 개체에는 스레드가 동기화를 달성하는 데 사용하는 암시적 모니터 잠금이 있으며 이 코드는 Calculator 개체를 synchronized() 함수에 전달하여 암시적 모니터 잠금을 사용합니다.

synchronized() 함수는 addIntArray() 메서드를 차례로 호출하는 sumUsingSynchronization() 함수에 의해 호출됩니다. main() 메서드에서 우리는 두 개의 스레드를 생성하고 실행을 시작하기 위해 스레드에서 start() 메서드를 호출했습니다.

우리 코드는 암시적 모니터 잠금을 사용하므로 두 스레드 간에 간섭이 없습니다. 첫 번째 스레드는 두 번째 스레드가 시작되기 전에 실행이 완료될 때까지 독점적으로 addIntArray() 메서드를 사용합니다.

synchronized() 함수는 암시적 모니터 잠금으로 제한될 뿐만 아니라 ReentrantLock과 같은 다른 잠금도 사용할 수 있으며 이 메서드의 인수로 잠금을 전달하기만 하면 됩니다. 이 코드를 실행하고 출력이 아래와 같은지 확인하십시오.

Thread-0 ---> 0 + 1 = 1
Thread-0 ---> 1 + 2 = 3
Thread-0 ---> 3 + 3 = 6
Thread-0 ---> 6 + 4 = 10
Thread-0 ---> 10 + 5 = 15
Thread-1 ---> 0 + 1 = 1
Thread-1 ---> 1 + 2 = 3
Thread-1 ---> 3 + 3 = 6
Thread-1 ---> 6 + 4 = 10
Thread-1 ---> 10 + 5 = 15

Kotlin에서 withLock() 함수를 사용하여 공유 리소스 잠금

이전 예제에서 생성한 main() 메서드를 주석 처리하고 주석 뒤에 다음 코드를 복사하여 Main.kt 파일에 붙여넣습니다.

import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock

val reentrantLock: Lock = ReentrantLock();

fun sumUsingLock(): Unit = reentrantLock.withLock {
    calculator.addIntArray();
}

fun main() {

    Thread {
       sumUsingLock();
    }.start();

    Thread {
        sumUsingLock()
    }.start();
}

이 코드에서는 Lock 인터페이스의 withLock() 확장 기능에 할당되는 sumUsingLock()이라는 메서드를 생성했습니다. withLock() 확장 함수는 제공된 잠금 구현을 사용하여 생성된 여러 스레드 각각에 대해 독점적으로 코드 블록을 실행합니다.

이 예제에서는 Lock 인터페이스를 구현하는 ReentrantLock을 사용하여 사용자 정의 잠금을 생성했습니다. 이 잠금은 addIntArray() 메서드를 배타적으로 실행하기 위해 생성된 스레드에서 사용됩니다.

이 코드를 실행하고 출력이 아래와 같은지 확인하십시오.

Thread-0 ---> 0 + 1 = 1
Thread-0 ---> 1 + 2 = 3
Thread-0 ---> 3 + 3 = 6
Thread-0 ---> 6 + 4 = 10
Thread-0 ---> 10 + 5 = 15
Thread-1 ---> 0 + 1 = 1
Thread-1 ---> 1 + 2 = 3
Thread-1 ---> 3 + 3 = 6
Thread-1 ---> 6 + 4 = 10
Thread-1 ---> 10 + 5 = 15

결론

이 자습서에서는 이러한 리소스에 대한 잠금이 생성되는 방법을 다루어 공유 리소스에 대한 액세스를 관리하는 방법을 배웠습니다. 간섭이 어떻게 발생하는지, synchronized() 기능으로 잠금이 사용되는 방법 및 withLock() 기능으로 잠금이 사용되는 방법을 다루었습니다.

이 두 접근 방식의 차이점은 synchronized() 함수는 잠금 구현으로 제한되지 않지만 withLock() 함수는 Lock 구현으로 제한된다는 것입니다.

David Mbochi Njonge avatar David Mbochi Njonge avatar

David is a back end developer with a major in computer science. He loves to solve problems using technology, learning new things, and making new friends. David is currently a technical writer who enjoys making hard concepts easier for other developers to understand and his work has been published on multiple sites.

LinkedIn GitHub