목표
- JVM 프로세스와 멀티 스레드 환경에 대해 이해한다.
- 공유되는 자원과 공유되지 않는 자원의 차이와 공유되는 자원의 접근 시 주의할점에 대해 이해한다.
개요
앞서 프로세스란 자원을 할당받아 실행되는 프로그램이라고 말했다. 즉, JVM은 하나의 프로세스이다. 하나의 프로세스는 여러 작업 단위를 가질 수 있는데 이 작업 단위를 바로 스레드라고 한다.
JVM에서는 Main Thread라 불리는 쓰레드가 있고, 우리가 main() 메서드를 사용해 불리는 것이 바로 Main Thread이다.
Main Thread가 종료되면 나머지 Thread 들도 자동으로 종료가 된다. 즉, JVM에서 Main Thread가 살아있는 것은 필수적이다. 하지만, 여러 Thread로 동시에 연산을 할 수 있음에도 Main Thread만을 사용하여 연산하는것은 비효율적이다. 이를 위해 JVM에서는 Multi Thread를 이용하여 데이터를 처리할 수 있는 방법을 만들어 놓았다.
하지만, 이렇게 Multi Thread를 이용하여 처리할 경우 문제가 생긴다. 변수는 Heap Area에 Load 되는데, 각 쓰레드가 Heap Area에 있는 변수에 동시 접근하여 변경할 경우 데이터가 의도한 것과 다르게 변동될 수 있기 때문이다.
이렇게 될 경우 더이상 예측한 것과 다른 결과가 나오게 되며, 이를 방지하기 위해 멀티 스레드에서 접근하는 변수는 아래와 같이 불변 변수로 선언하거나(코드1), 가변 변수일 경우 해당 값을 변경하는 연산을 @Synchronized 키워드를 이용해 한번에 하나의 스레드에서만 접근하게 막아두어야 한다(코드2).
val immutableString = "Kotlin World"
코드1. 불변 변수로 선언
@Synchronized
fun append(str: String?): StringBuffer? {
toStringCache = null
super.append(str)
return this
}
코드2. @Synchronized로 동시성 제어
이 두가지 방법을 통해 동시 접근 시 데이터 완전성을 보장할 수 있다. 가장 좋은 방법은 불변 변수로 선언하고 멀티스레드에서 접근하는 변수는 변화하지 않게 하는 것이다. 하지만, 그게 어려울 경우 @Synchronized 를 붙여 불변 변수로 선언한다.
경향
메인스레드만 사용할 경우 다음과 같은 문제점이 생긴다.
- 프로그램이 멈춘 것처럼 보일 수 있음 (특히 안드로이드에서 UI 끊김 현상)
- 작업이 끝날 때까지 다른 작업을 할 수 없음
최근 CPU는 기본적으로 여러개의 코어를 가지고 있어 여러 개의 스레드를 활용하는 프로그래밍이 가능하다. 따라서 멀티스레드를 활용하는 비동기 프로그래밍이 요즘 경향이다. 비동기 프로그래밍이란, 연산을 다른 스레드로 넘겨버리고, 현재 스레드에서는 다른 작업을 할 수 있도록 하는 프로그래밍 방식이다.
이를 지원하기 위한 RxJava나 Coroutine과 같은 라이브러리가 나와있다. 이에 대해서는 다른 글에서 다루도록 하겠다.
이러한 비동기 프로그래밍의 이점은 메인 스레드를 blocking 하지 않고 다른 스레드에서 작업을 수행하는 것이 가능하다는 것이다. 많은 스레드를 활용할 경우 연산 속도 또한 줄일 수 있다. 특히 UI가 있는 프로그램에서는 보통 메인스레드가 UI를 제어하게 되는데 메인 스레드가 끊기지 않을 경우 UI가 끊기지 않아 사용자 경험 또한 개선시킬 수 있다.
정리
- JVM의 메인스레드가 죽으면 JVM도 죽는다.
- 하나의 JVM(프로세스)은 여러 스레드를 가질 수 있다.
- 변수는 모든 스레드가 공통으로 사용하는 Heap Area에 올라가기 때문에 멀티스레드 환경에서 가변 변수를 변화 시킬 경우 데이터 완전성이 깨질 수 있다.
- 이를 방지하기 위해 멀티스레드 환경에서 사용하는 변수는 불변변수로 사용하거나 가변변수로 사용할 경우 @Synchronized 키워드를 이용한다.
관련글